diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 21:49:38 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 21:49:38 +0000 |
commit | d7cae12696b96500c05dd2d430f6238922c20c96 (patch) | |
tree | ecff27b367735535b2a66477f8cd89d3c462a6c0 | |
parent | ee2815e28d408216cf94e874825b6bcf76c69083 (diff) | |
download | chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.zip chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.tar.gz chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.tar.bz2 |
Add base to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8 0039d316-1c4b-4281-b951-d872f2087c98
371 files changed, 60061 insertions, 0 deletions
diff --git a/base/SConscript b/base/SConscript new file mode 100644 index 0000000..2b7a7cc --- /dev/null +++ b/base/SConscript @@ -0,0 +1,296 @@ +# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('env')
+
+env = env.Clone()
+env_tests = env.Clone()
+
+env.Prepend(
+ CPPPATH = [
+ '$ICU38_DIR/public/common',
+ '$ICU38_DIR/public/i18n',
+ '..',
+ ],
+ CPPDEFINES = [
+ 'U_STATIC_IMPLEMENTATION',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ 'WIN32_LEAN_AND_MEAN',
+ ],
+ CCFLAGS = [
+ '/TP',
+
+ '/Wp64',
+
+ '/wd4503',
+ '/wd4819',
+ ],
+)
+
+input_files = [
+ 'base_drag_source.cc',
+ 'base_drop_target.cc',
+ 'base_paths.cc',
+ 'base_switches.cc',
+ 'clipboard.cc',
+ 'clipboard_util.cc',
+ 'command_line.cc',
+ 'condition_variable.cc',
+ 'debug_on_start.cc',
+ 'debug_util.cc',
+ 'event_recorder.cc',
+ 'file_util.cc',
+ 'file_version_info.cc',
+ 'histogram.cc',
+ 'hmac.cc',
+ 'iat_patch.cc',
+ 'icu_util.cc',
+ 'idle_timer.cc',
+ 'image_util.cc',
+ 'json_reader.cc',
+ 'json_writer.cc',
+ 'lock.cc',
+ 'lock_impl_win.cc',
+ 'logging.cc',
+ 'md5.cc',
+ 'memory_debug.cc',
+ 'message_loop.cc',
+ 'non_thread_safe.cc',
+ 'path_service.cc',
+ 'pe_image.cc',
+ 'pickle.cc',
+ 'platform_thread.cc',
+ 'process.cc',
+ 'process_util.cc',
+ 'registry.cc',
+ 'resource_util.cc',
+ 'revocable_store.cc',
+ 'sha2.cc',
+ 'shared_event.cc',
+ 'shared_memory.cc',
+ 'stats_table.cc',
+ 'string_escape.cc',
+ 'string_util.cc',
+ 'string_util_icu.cc',
+ 'string_util_win.cc',
+ 'third_party/nspr/prtime.cc',
+ 'third_party/nss/sha512.cc',
+ 'thread.cc',
+ 'thread_local_storage_win.cc',
+ 'time.cc',
+ 'time_win.cc',
+ 'timer.cc',
+ 'tracked.cc',
+ 'tracked_objects.cc',
+ 'values.cc',
+ 'watchdog.cc',
+ 'win_util.cc',
+ 'wmi_util.cc',
+ 'word_iterator.cc',
+ 'worker_pool.cc',
+]
+
+env.StaticLibrary('base', input_files)
+
+
+env_tests.Prepend(
+ CPPPATH = [
+ '$SKIA_DIR/include',
+ '$SKIA_DIR/include/corecg',
+ '$SKIA_DIR/platform',
+ '$ZLIB_DIR',
+ '$LIBPNG_DIR',
+ '$ICU38_DIR/public/common',
+ '$ICU38/_DIRpublic/i18n',
+ '..',
+ ],
+ CPPDEFINES = [
+ 'UNIT_TEST',
+ 'PNG_USER_CONFIG',
+ 'CHROME_PNG_WRITE_SUPPORT',
+ 'U_STATIC_IMPLEMENTATION',
+ '_WIN32_WINNT=0x0600',
+ 'WINVER=0x0600',
+ '_HAS_EXCEPTIONS=0',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ 'WIN32_LEAN_AND_MEAN',
+ ],
+ CCFLAGS = [
+ '/WX',
+ '/Wp64',
+ '/TP',
+
+ '/wd4503',
+ '/wd4819',
+ ],
+ LINKFLAGS = [
+ '/MANIFEST',
+ '/DELAYLOAD:"dwmapi.dll"',
+ '/DELAYLOAD:"uxtheme.dll"',
+ '/MACHINE:X86',
+ '/FIXED:No',
+
+ '/safeseh',
+ '/dynamicbase',
+ '/ignore:4199',
+ '/nxcompat',
+ ],
+ LIBS = [
+ 'advapi32.lib',
+ 'comdlg32.lib',
+ 'DelayImp.lib',
+ 'gdi32.lib',
+ 'kernel32.lib',
+ 'msimg32.lib',
+ 'odbc32.lib',
+ 'odbccp32.lib',
+ 'ole32.lib',
+ 'oleaut32.lib',
+ 'psapi.lib',
+ 'shell32.lib',
+ 'user32.lib',
+ 'usp10.lib',
+ 'uuid.lib',
+ 'version.lib',
+ 'wininet.lib',
+ 'winspool.lib',
+ 'ws2_32.lib',
+ ],
+)
+
+libs = [
+ 'base.lib',
+ 'gfx/base_gfx.lib',
+ '$SKIA_DIR/skia.lib',
+ '$LIBPNG_DIR/libpng.lib',
+ '$TESTING_DIR/gtest.lib',
+ '$ICU38_DIR/icuuc.lib',
+ '$ZLIB_DIR/zlib.lib',
+]
+
+env_tests.Append(
+ CPPPATH = [
+ '$GTEST_DIR/include',
+ ],
+)
+
+env_tests_dll = env_tests.Clone()
+env_tests_dll.Append(
+ CPPDEFINES = [
+ '_WINDLL',
+ 'SINGLETON_UNITTEST_EXPORTS',
+ ],
+)
+dll = env_tests_dll.SharedLibrary(['singleton_dll_unittest.dll',
+ 'singleton_dll_unittest.ilk',
+ 'singleton_dll_unittest.pdb'],
+ ['singleton_dll_unittest.cc',
+ 'build/singleton_dll_unittest.def'] + libs)
+i = env.Install('$TARGET_ROOT', dll[0])
+env.Alias('base', i)
+
+env_tests.Program(['debug_message.exe',
+ 'debug_message.ilk',
+ 'debug_message.pdb'],
+ ['debug_message.cc'] + libs)
+
+test_files = [
+ 'atomic_unittest.cc',
+ 'check_handler_unittest.cc',
+ 'clipboard_unittest.cc',
+ 'command_line_unittest.cc',
+ 'condition_variable_test.cc',
+ 'file_util_unittest.cc',
+ 'file_version_info_unittest.cc',
+ 'fixed_string_unittest.cc',
+ 'gfx/convolver_unittest.cc',
+ 'gfx/image_operations_unittest.cc',
+ 'gfx/native_theme_unittest.cc',
+ 'gfx/platform_canvas_unittest.cc',
+ 'gfx/png_codec_unittest.cc',
+ 'gfx/rect_unittest.cc',
+ 'gfx/uniscribe_unittest.cc',
+ 'gfx/vector_canvas_unittest.cc',
+ 'hmac_unittest.cc',
+ 'json_reader_unittest.cc',
+ 'json_writer_unittest.cc',
+ 'linked_ptr_unittest.cc',
+ 'message_loop_unittest.cc',
+ 'path_service_unittest.cc',
+ 'pe_image_unittest.cc',
+ 'pickle_unittest.cc',
+ 'pr_time_test.cc',
+ 'process_util_unittest.cc',
+ 'ref_counted_unittest.cc',
+ 'run_all_unittests.cc',
+ 'sha2_unittest.cc',
+ 'shared_event_unittest.cc',
+ 'shared_memory_unittest.cc',
+ 'singleton_unittest.cc',
+ 'stack_container_unittest.cc',
+ 'stats_table_unittest.cc',
+ 'string_tokenizer_unittest.cc',
+ 'string_util_unittest.cc',
+ 'thread_local_storage_unittest.cc',
+ 'thread_unittest.cc',
+ 'tracked_objects_test.cc',
+ 'time_unittest.cc',
+ 'timer_unittest.cc',
+ 'values_unittest.cc',
+ 'win_util_unittest.cc',
+ 'wmi_util_unittest.cc',
+
+ 'singleton_dll_unittest.lib',
+]
+
+base_unittests = env_tests.Program([
+ 'base_unittests',
+ 'base_unittests.exp',
+ 'base_unittests.ilk',
+ 'base_unittests.lib',
+ 'base_unittests.pdb'], test_files + libs)
+
+
+# Install up a level to allow unit test path assumptions to be valid.
+installed_base_unittests = env.Install('$TARGET_ROOT', base_unittests)
+
+
+sconscript_dirs = [
+ 'gfx/SConscript',
+]
+
+SConscript(sconscript_dirs, exports=['env'])
+
+
+# Setup alias for all base related targets.
+env.Alias('base', ['.', installed_base_unittests, '../icudt38.dll'])
+
+
+env_tests.StaticObject('perftimer.cc')
+env_tests.StaticObject('run_all_perftests.cc')
diff --git a/base/SConstruct b/base/SConstruct new file mode 100644 index 0000000..e9acd54 --- /dev/null +++ b/base/SConstruct @@ -0,0 +1,3 @@ +build_component = 'base'
+SConscript('../build/SConscript.main',
+ exports=['build_component'])
diff --git a/base/atomic.h b/base/atomic.h new file mode 100644 index 0000000..becbcc3 --- /dev/null +++ b/base/atomic.h @@ -0,0 +1,111 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_ATOMIC_H__ +#define BASE_ATOMIC_H__ + +#if defined(WIN32) +#include <windows.h> +#elif defined(__APPLE__) +#include <libkern/OSAtomic.h> +#else +#error Implement atomic support on your platform +#endif + +#include "base/basictypes.h" + +namespace base { + +/////////////////////////////////////////////////////////////////////////// +// +// Forward declarations and descriptions of the functions +// +/////////////////////////////////////////////////////////////////////////// + +// Atomically increments *value and returns the resulting incremented value. +// This function implies no memory barriers. +int32 AtomicIncrement(volatile int32* value); + +// Atomically decrements *value and returns the resulting decremented value. +// This function implies no memory barriers. +int32 AtomicDecrement(volatile int32* value); + +// Atomically sets *target to new_value and returns the old value of *target. +// This function implies no memory barriers. +int32 AtomicSwap(volatile int32* target, int32 new_value); + +/////////////////////////////////////////////////////////////////////////// +// +// Implementations for various platforms +// +/////////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) + +inline int32 AtomicIncrement(volatile int32* value) { + return InterlockedIncrement(reinterpret_cast<volatile LONG*>(value)); +} + +inline int32 AtomicDecrement(volatile int32* value) { + return InterlockedDecrement(reinterpret_cast<volatile LONG*>(value)); +} + +inline int32 AtomicSwap(volatile int32* target, int32 new_value) { + return InterlockedExchange(reinterpret_cast<volatile LONG*>(target), + new_value); +} + +#elif defined(__APPLE__) + +inline int32 AtomicIncrement(volatile int32* value) { + return OSAtomicIncrement32(reinterpret_cast<volatile int32_t*>(value)); +} + +inline int32 AtomicDecrement(volatile int32* value) { + return OSAtomicDecrement32(reinterpret_cast<volatile int32_t*>(value)); +} + +inline int32 AtomicSwap(volatile int32* target, int32 new_value) { + int32 old_value; + do { + old_value = *target; + } while (!OSAtomicCompareAndSwap32(old_value, new_value, + reinterpret_cast<volatile int32_t*>(target))); + return old_value; +} + +#else + +#error Implement atomic support on your platform + +#endif + +} // namespace base + +#endif // BASE_ATOMIC_H__ diff --git a/base/atomic_unittest.cc b/base/atomic_unittest.cc new file mode 100644 index 0000000..770a520 --- /dev/null +++ b/base/atomic_unittest.cc @@ -0,0 +1,63 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/atomic.h" + +#include "base/lock.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(AtomicTest, Increment) { + int32 value = 38; + int32 new_value = base::AtomicIncrement(&value); + EXPECT_EQ(39, value); + EXPECT_EQ(39, new_value); +} + +TEST(AtomicTest, Decrement) { + int32 value = 49; + int32 new_value = base::AtomicDecrement(&value); + EXPECT_EQ(48, value); + EXPECT_EQ(48, new_value); +} + +TEST(AtomicTest, Swap) { + int32 value = 38; + int32 old_value = base::AtomicSwap(&value, 49); + EXPECT_EQ(49, value); + EXPECT_EQ(38, old_value); + + // Now do another test. + value = 0; + old_value = base::AtomicSwap(&value, 1); + EXPECT_EQ(0, old_value); + old_value = base::AtomicSwap(&value, 1); + EXPECT_EQ(1, old_value); + old_value = base::AtomicSwap(&value, 1); + EXPECT_EQ(1, old_value); +} diff --git a/base/base.sln b/base/base.sln new file mode 100644 index 0000000..c928064 --- /dev/null +++ b/base/base.sln @@ -0,0 +1,109 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "base", "build\base.vcproj", "{1832A374-8A74-4F9E-B536-69A699B3E165}" + ProjectSection(ProjectDependencies) = postProject + {0E5474AC-5996-4B13-87C0-4AE931EE0815} = {0E5474AC-5996-4B13-87C0-4AE931EE0815} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "base_unittests", "build\base_unittests.vcproj", "{27A30967-4BBA-48D1-8522-CDE95F7B1CEC}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C} = {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C} + {8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308} + {A508ADD3-CECE-4E0F-8448-2F5E454DF551} = {A508ADD3-CECE-4E0F-8448-2F5E454DF551} + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} + {C564F145-9172-42C3-BFCB-6014CA97DBCD} = {C564F145-9172-42C3-BFCB-6014CA97DBCD} + {CD9CA56E-4E94-444C-87D4-58CA1E6F300D} = {CD9CA56E-4E94-444C-87D4-58CA1E6F300D} + {E457F2FB-4708-4001-9B1C-275D7BD7F2A8} = {E457F2FB-4708-4001-9B1C-275D7BD7F2A8} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debug_message", "build\debug_message.vcproj", "{0E5474AC-5996-4B13-87C0-4AE931EE0815}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencies", "dependencies", "{F216062D-F9C4-4883-A52C-2BE9ECADEEA0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "icuuc", "..\third_party\icu38\build\icuuc.vcproj", "{8C27D792-2648-4F5E-9ED0-374276327308}" + ProjectSection(ProjectDependencies) = postProject + {A0D94973-D355-47A5-A1E2-3456F321F010} = {A0D94973-D355-47A5-A1E2-3456F321F010} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "icudt", "..\third_party\icu38\build\icudt.vcproj", "{A0D94973-D355-47A5-A1E2-3456F321F010}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "base_gfx", "build\base_gfx.vcproj", "{A508ADD3-CECE-4E0F-8448-2F5E454DF551}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "skia", "..\skia\skia.vcproj", "{CD9CA56E-4E94-444C-87D4-58CA1E6F300D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "..\third_party\libpng\libpng.vcproj", "{C564F145-9172-42C3-BFCB-6014CA97DBCD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\third_party\zlib\zlib.vcproj", "{8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "singleton_dll_unittest", "build\singleton_unittest.vcproj", "{E457F2FB-4708-4001-9B1C-275D7BD7F2A8}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtest", "..\testing\gtest.vcproj", "{BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Debug|Win32.ActiveCfg = Debug|Win32 + {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Debug|Win32.Build.0 = Debug|Win32 + {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Release|Win32.ActiveCfg = Release|Win32 + {0E5474AC-5996-4B13-87C0-4AE931EE0815}.Release|Win32.Build.0 = Release|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Debug|Win32.ActiveCfg = Debug|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Debug|Win32.Build.0 = Debug|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Release|Win32.ActiveCfg = Release|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Release|Win32.Build.0 = Release|Win32 + {27A30967-4BBA-48D1-8522-CDE95F7B1CEC}.Debug|Win32.ActiveCfg = Debug|Win32 + {27A30967-4BBA-48D1-8522-CDE95F7B1CEC}.Debug|Win32.Build.0 = Debug|Win32 + {27A30967-4BBA-48D1-8522-CDE95F7B1CEC}.Release|Win32.ActiveCfg = Release|Win32 + {27A30967-4BBA-48D1-8522-CDE95F7B1CEC}.Release|Win32.Build.0 = Release|Win32 + {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Debug|Win32.ActiveCfg = Debug|Win32 + {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Debug|Win32.Build.0 = Debug|Win32 + {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Release|Win32.ActiveCfg = Release|Win32 + {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C}.Release|Win32.Build.0 = Release|Win32 + {8C27D792-2648-4F5E-9ED0-374276327308}.Debug|Win32.ActiveCfg = Debug|Win32 + {8C27D792-2648-4F5E-9ED0-374276327308}.Debug|Win32.Build.0 = Debug|Win32 + {8C27D792-2648-4F5E-9ED0-374276327308}.Release|Win32.ActiveCfg = Release|Win32 + {8C27D792-2648-4F5E-9ED0-374276327308}.Release|Win32.Build.0 = Release|Win32 + {A0D94973-D355-47A5-A1E2-3456F321F010}.Debug|Win32.ActiveCfg = Debug|Win32 + {A0D94973-D355-47A5-A1E2-3456F321F010}.Debug|Win32.Build.0 = Debug|Win32 + {A0D94973-D355-47A5-A1E2-3456F321F010}.Release|Win32.ActiveCfg = Release|Win32 + {A0D94973-D355-47A5-A1E2-3456F321F010}.Release|Win32.Build.0 = Release|Win32 + {A508ADD3-CECE-4E0F-8448-2F5E454DF551}.Debug|Win32.ActiveCfg = Debug|Win32 + {A508ADD3-CECE-4E0F-8448-2F5E454DF551}.Debug|Win32.Build.0 = Debug|Win32 + {A508ADD3-CECE-4E0F-8448-2F5E454DF551}.Release|Win32.ActiveCfg = Release|Win32 + {A508ADD3-CECE-4E0F-8448-2F5E454DF551}.Release|Win32.Build.0 = Release|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Debug|Win32.ActiveCfg = Debug|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Debug|Win32.Build.0 = Debug|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Release|Win32.ActiveCfg = Release|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Release|Win32.Build.0 = Release|Win32 + {C564F145-9172-42C3-BFCB-6014CA97DBCD}.Debug|Win32.ActiveCfg = Debug|Win32 + {C564F145-9172-42C3-BFCB-6014CA97DBCD}.Debug|Win32.Build.0 = Debug|Win32 + {C564F145-9172-42C3-BFCB-6014CA97DBCD}.Release|Win32.ActiveCfg = Release|Win32 + {C564F145-9172-42C3-BFCB-6014CA97DBCD}.Release|Win32.Build.0 = Release|Win32 + {CD9CA56E-4E94-444C-87D4-58CA1E6F300D}.Debug|Win32.ActiveCfg = Debug|Win32 + {CD9CA56E-4E94-444C-87D4-58CA1E6F300D}.Debug|Win32.Build.0 = Debug|Win32 + {CD9CA56E-4E94-444C-87D4-58CA1E6F300D}.Release|Win32.ActiveCfg = Release|Win32 + {CD9CA56E-4E94-444C-87D4-58CA1E6F300D}.Release|Win32.Build.0 = Release|Win32 + {E457F2FB-4708-4001-9B1C-275D7BD7F2A8}.Debug|Win32.ActiveCfg = Debug|Win32 + {E457F2FB-4708-4001-9B1C-275D7BD7F2A8}.Debug|Win32.Build.0 = Debug|Win32 + {E457F2FB-4708-4001-9B1C-275D7BD7F2A8}.Release|Win32.ActiveCfg = Release|Win32 + {E457F2FB-4708-4001-9B1C-275D7BD7F2A8}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8423AF0D-4B88-4EBF-94E1-E4D00D00E21C} = {F216062D-F9C4-4883-A52C-2BE9ECADEEA0} + {8C27D792-2648-4F5E-9ED0-374276327308} = {F216062D-F9C4-4883-A52C-2BE9ECADEEA0} + {A0D94973-D355-47A5-A1E2-3456F321F010} = {F216062D-F9C4-4883-A52C-2BE9ECADEEA0} + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {F216062D-F9C4-4883-A52C-2BE9ECADEEA0} + {C564F145-9172-42C3-BFCB-6014CA97DBCD} = {F216062D-F9C4-4883-A52C-2BE9ECADEEA0} + {CD9CA56E-4E94-444C-87D4-58CA1E6F300D} = {F216062D-F9C4-4883-A52C-2BE9ECADEEA0} + EndGlobalSection +EndGlobal diff --git a/base/base.xcodeproj/project.pbxproj b/base/base.xcodeproj/project.pbxproj new file mode 100644 index 0000000..64eaff5 --- /dev/null +++ b/base/base.xcodeproj/project.pbxproj @@ -0,0 +1,970 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXAggregateTarget section */ + 825404020D92D3340006B936 /* All */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 825404110D92D35C0006B936 /* Build configuration list for PBXAggregateTarget "All" */; + buildPhases = ( + ); + dependencies = ( + 825404060D92D33A0006B936 /* PBXTargetDependency */, + 825404080D92D33C0006B936 /* PBXTargetDependency */, + ); + name = All; + productName = All; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 7B5AD60E0D9DD8050012BCF1 /* scoped_cftyperef.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B5AD60D0D9DD8050012BCF1 /* scoped_cftyperef.h */; }; + 7BD9E84F0DA447F800FC7A01 /* singleton.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BD9E84E0DA447F800FC7A01 /* singleton.h */; }; + 7BEB81110D9AD288009BA8DD /* prcpucfg_mac.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BEB81100D9AD288009BA8DD /* prcpucfg_mac.h */; }; + 7BEB814A0D9B0F33009BA8DD /* time_mac.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BEB81490D9B0F33009BA8DD /* time_mac.cc */; }; + 7BEB834E0D9C4BE0009BA8DD /* string_util_mac.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BEB834D0D9C4BE0009BA8DD /* string_util_mac.cc */; }; + 7BEE52C20D9D84FD0067FF23 /* string_util_mac.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BEE52C10D9D84FD0067FF23 /* string_util_mac.h */; }; + 7BEFC29E0D99832D000829AD /* lock_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BEFC29C0D99832D000829AD /* lock_impl.h */; }; + 7BEFC29F0D99832D000829AD /* lock_impl_mac.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BEFC29D0D99832D000829AD /* lock_impl_mac.cc */; }; + 821B91690DAABD7F00F350D7 /* string16.h in Headers */ = {isa = PBXBuildFile; fileRef = 821B91680DAABD7F00F350D7 /* string16.h */; }; + 825402CE0D92D1390006B936 /* base_drag_source.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402C50D92D1390006B936 /* base_drag_source.cc */; }; + 825402CF0D92D1390006B936 /* base_drag_source.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402C60D92D1390006B936 /* base_drag_source.h */; }; + 825402D00D92D1390006B936 /* base_drop_target.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402C70D92D1390006B936 /* base_drop_target.cc */; }; + 825402D10D92D1390006B936 /* base_drop_target.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402C80D92D1390006B936 /* base_drop_target.h */; }; + 825402D20D92D1390006B936 /* base_paths.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402C90D92D1390006B936 /* base_paths.cc */; }; + 825402D30D92D1390006B936 /* base_paths.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402CA0D92D1390006B936 /* base_paths.h */; }; + 825402D40D92D1390006B936 /* base_switches.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402CB0D92D1390006B936 /* base_switches.cc */; }; + 825402D50D92D1390006B936 /* base_switches.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402CC0D92D1390006B936 /* base_switches.h */; }; + 825402D60D92D1390006B936 /* basictypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402CD0D92D1390006B936 /* basictypes.h */; }; + 825402D90D92D15E0006B936 /* blapi.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402D70D92D15E0006B936 /* blapi.h */; }; + 825402DA0D92D15E0006B936 /* blapit.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402D80D92D15E0006B936 /* blapit.h */; }; + 825402DF0D92D1730006B936 /* clipboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402DB0D92D1730006B936 /* clipboard.h */; }; + 825402E00D92D1730006B936 /* clipboard.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402DC0D92D1730006B936 /* clipboard.cc */; }; + 825402E10D92D1730006B936 /* clipboard_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402DD0D92D1730006B936 /* clipboard_util.h */; }; + 825402E20D92D1730006B936 /* clipboard_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402DE0D92D1730006B936 /* clipboard_util.cc */; }; + 825402E50D92D1850006B936 /* command_line.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402E30D92D1850006B936 /* command_line.cc */; }; + 825402E60D92D1850006B936 /* command_line.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402E40D92D1850006B936 /* command_line.h */; }; + 825402EE0D92D1940006B936 /* condition_variable.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402EB0D92D1940006B936 /* condition_variable.h */; }; + 825402EF0D92D1940006B936 /* condition_variable.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402EC0D92D1940006B936 /* condition_variable.cc */; }; + 825402F00D92D1940006B936 /* condition_variable_events.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402ED0D92D1940006B936 /* condition_variable_events.h */; }; + 825402F50D92D1AC0006B936 /* debug_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402F10D92D1AC0006B936 /* debug_util.h */; }; + 825402F60D92D1AC0006B936 /* debug_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402F20D92D1AC0006B936 /* debug_util.cc */; }; + 825402F70D92D1AC0006B936 /* debug_on_start.h in Headers */ = {isa = PBXBuildFile; fileRef = 825402F30D92D1AC0006B936 /* debug_on_start.h */; }; + 825402F80D92D1AC0006B936 /* debug_on_start.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402F40D92D1AC0006B936 /* debug_on_start.cc */; }; + 825403010D92D1BC0006B936 /* event_recorder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825402FF0D92D1BC0006B936 /* event_recorder.cc */; }; + 825403020D92D1BC0006B936 /* event_recorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403000D92D1BC0006B936 /* event_recorder.h */; }; + 825403050D92D1C50006B936 /* file_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403030D92D1C50006B936 /* file_util.h */; }; + 825403060D92D1C50006B936 /* file_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403040D92D1C50006B936 /* file_util.cc */; }; + 825403090D92D1CD0006B936 /* file_version_info.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403070D92D1CD0006B936 /* file_version_info.h */; }; + 8254030A0D92D1CD0006B936 /* file_version_info.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403080D92D1CD0006B936 /* file_version_info.cc */; }; + 8254030C0D92D1D10006B936 /* fix_wp64.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254030B0D92D1D10006B936 /* fix_wp64.h */; }; + 8254030E0D92D1DB0006B936 /* fixed_string.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254030D0D92D1DB0006B936 /* fixed_string.h */; }; + 825403140D92D1E80006B936 /* iat_patch.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254030F0D92D1E80006B936 /* iat_patch.cc */; }; + 825403150D92D1E80006B936 /* iat_patch.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403100D92D1E80006B936 /* iat_patch.h */; }; + 825403160D92D1E80006B936 /* icu_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403110D92D1E80006B936 /* icu_util.cc */; }; + 825403170D92D1E80006B936 /* icu_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403120D92D1E80006B936 /* icu_util.h */; }; + 825403180D92D1E80006B936 /* id_map.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403130D92D1E80006B936 /* id_map.h */; }; + 8254031F0D92D1F40006B936 /* image_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403190D92D1F40006B936 /* image_util.cc */; }; + 825403200D92D1F40006B936 /* image_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254031A0D92D1F40006B936 /* image_util.h */; }; + 825403210D92D1F40006B936 /* json_reader.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254031B0D92D1F40006B936 /* json_reader.cc */; }; + 825403220D92D1F40006B936 /* json_reader.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254031C0D92D1F40006B936 /* json_reader.h */; }; + 825403230D92D1F40006B936 /* json_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254031D0D92D1F40006B936 /* json_writer.cc */; }; + 825403240D92D1F40006B936 /* json_writer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254031E0D92D1F40006B936 /* json_writer.h */; }; + 8254032D0D92D2090006B936 /* lock.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403250D92D2090006B936 /* lock.cc */; }; + 8254032E0D92D2090006B936 /* lock.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403260D92D2090006B936 /* lock.h */; }; + 8254032F0D92D2090006B936 /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403270D92D2090006B936 /* logging.cc */; }; + 825403300D92D2090006B936 /* logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403280D92D2090006B936 /* logging.h */; }; + 825403310D92D2090006B936 /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403290D92D2090006B936 /* md5.cc */; }; + 825403320D92D2090006B936 /* md5.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254032A0D92D2090006B936 /* md5.h */; }; + 825403330D92D2090006B936 /* memory_debug.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254032B0D92D2090006B936 /* memory_debug.cc */; }; + 825403340D92D2090006B936 /* memory_debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254032C0D92D2090006B936 /* memory_debug.h */; }; + 825403370D92D2110006B936 /* message_loop.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403350D92D2110006B936 /* message_loop.cc */; }; + 825403380D92D2110006B936 /* message_loop.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403360D92D2110006B936 /* message_loop.h */; }; + 825403410D92D2210006B936 /* observer_list.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403390D92D2210006B936 /* observer_list.h */; }; + 825403420D92D2210006B936 /* path_service.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254033A0D92D2210006B936 /* path_service.cc */; }; + 825403430D92D2210006B936 /* path_service.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254033B0D92D2210006B936 /* path_service.h */; }; + 825403440D92D2210006B936 /* pe_image.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254033C0D92D2210006B936 /* pe_image.cc */; }; + 825403450D92D2210006B936 /* pe_image.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254033D0D92D2210006B936 /* pe_image.h */; }; + 825403460D92D2210006B936 /* pickle.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254033E0D92D2210006B936 /* pickle.cc */; }; + 825403470D92D2210006B936 /* pickle.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254033F0D92D2210006B936 /* pickle.h */; }; + 825403480D92D2210006B936 /* port.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403400D92D2210006B936 /* port.h */; }; + 8254034D0D92D23C0006B936 /* prtypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403490D92D23C0006B936 /* prtypes.h */; }; + 8254034E0D92D23C0006B936 /* prtime.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254034A0D92D23C0006B936 /* prtime.h */; }; + 8254034F0D92D23C0006B936 /* prtime.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254034B0D92D23C0006B936 /* prtime.cc */; }; + 825403500D92D23C0006B936 /* prcpucfg.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254034C0D92D23C0006B936 /* prcpucfg.h */; }; + 825403530D92D24D0006B936 /* process_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403510D92D24D0006B936 /* process_util.h */; }; + 825403540D92D24D0006B936 /* process_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403520D92D24D0006B936 /* process_util.cc */; }; + 825403560D92D2580006B936 /* pure.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403550D92D2580006B936 /* pure.h */; }; + 825403580D92D25E0006B936 /* pure_api.c in Sources */ = {isa = PBXBuildFile; fileRef = 825403570D92D25E0006B936 /* pure_api.c */; }; + 825403640D92D27C0006B936 /* ref_counted.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403590D92D27C0006B936 /* ref_counted.h */; }; + 825403650D92D27C0006B936 /* registry.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254035A0D92D27C0006B936 /* registry.cc */; }; + 825403660D92D27C0006B936 /* registry.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254035B0D92D27C0006B936 /* registry.h */; }; + 825403670D92D27C0006B936 /* resource_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254035C0D92D27C0006B936 /* resource_util.cc */; }; + 825403680D92D27C0006B936 /* resource_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254035D0D92D27C0006B936 /* resource_util.h */; }; + 825403690D92D27C0006B936 /* revocable_store.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254035E0D92D27C0006B936 /* revocable_store.cc */; }; + 8254036A0D92D27C0006B936 /* revocable_store.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254035F0D92D27C0006B936 /* revocable_store.h */; }; + 8254036C0D92D27C0006B936 /* scoped_ptr.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403610D92D27C0006B936 /* scoped_ptr.h */; }; + 8254036D0D92D27C0006B936 /* sha2.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403620D92D27C0006B936 /* sha2.cc */; }; + 8254036E0D92D27C0006B936 /* sha2.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403630D92D27C0006B936 /* sha2.h */; }; + 825403710D92D2840006B936 /* sha256.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254036F0D92D2840006B936 /* sha256.h */; }; + 825403720D92D2840006B936 /* sha512.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403700D92D2840006B936 /* sha512.cc */; }; + 825403900D92D2CF0006B936 /* shared_event.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403730D92D2CF0006B936 /* shared_event.cc */; }; + 825403910D92D2CF0006B936 /* shared_event.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403740D92D2CF0006B936 /* shared_event.h */; }; + 825403920D92D2CF0006B936 /* shared_memory.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403750D92D2CF0006B936 /* shared_memory.cc */; }; + 825403930D92D2CF0006B936 /* shared_memory.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403760D92D2CF0006B936 /* shared_memory.h */; }; + 825403940D92D2CF0006B936 /* stack_container.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403770D92D2CF0006B936 /* stack_container.h */; }; + 825403950D92D2CF0006B936 /* stats_counters.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403780D92D2CF0006B936 /* stats_counters.h */; }; + 825403960D92D2CF0006B936 /* stats_table.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403790D92D2CF0006B936 /* stats_table.cc */; }; + 825403970D92D2CF0006B936 /* stats_table.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254037A0D92D2CF0006B936 /* stats_table.h */; }; + 825403980D92D2CF0006B936 /* string_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254037B0D92D2CF0006B936 /* string_tokenizer.h */; }; + 825403990D92D2CF0006B936 /* string_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254037C0D92D2CF0006B936 /* string_util.cc */; }; + 8254039A0D92D2CF0006B936 /* string_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254037D0D92D2CF0006B936 /* string_util.h */; }; + 8254039B0D92D2CF0006B936 /* task.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254037E0D92D2CF0006B936 /* task.h */; }; + 8254039C0D92D2CF0006B936 /* thread.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254037F0D92D2CF0006B936 /* thread.cc */; }; + 8254039D0D92D2CF0006B936 /* thread.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403800D92D2CF0006B936 /* thread.h */; }; + 8254039E0D92D2CF0006B936 /* thread_local_storage.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403810D92D2CF0006B936 /* thread_local_storage.cc */; }; + 8254039F0D92D2CF0006B936 /* thread_local_storage.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403820D92D2CF0006B936 /* thread_local_storage.h */; }; + 825403A00D92D2CF0006B936 /* time.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403830D92D2CF0006B936 /* time.cc */; }; + 825403A10D92D2CF0006B936 /* time.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403840D92D2CF0006B936 /* time.h */; }; + 825403A20D92D2CF0006B936 /* timer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403850D92D2CF0006B936 /* timer.cc */; }; + 825403A30D92D2CF0006B936 /* timer.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403860D92D2CF0006B936 /* timer.h */; }; + 825403A40D92D2CF0006B936 /* tuple.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403870D92D2CF0006B936 /* tuple.h */; }; + 825403A50D92D2CF0006B936 /* values.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403880D92D2CF0006B936 /* values.cc */; }; + 825403A60D92D2CF0006B936 /* values.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403890D92D2CF0006B936 /* values.h */; }; + 825403A70D92D2CF0006B936 /* win_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254038A0D92D2CF0006B936 /* win_util.cc */; }; + 825403A80D92D2CF0006B936 /* win_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254038B0D92D2CF0006B936 /* win_util.h */; }; + 825403A90D92D2CF0006B936 /* wmi_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254038C0D92D2CF0006B936 /* wmi_util.cc */; }; + 825403AA0D92D2CF0006B936 /* wmi_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254038D0D92D2CF0006B936 /* wmi_util.h */; }; + 825403AB0D92D2CF0006B936 /* word_iterator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8254038E0D92D2CF0006B936 /* word_iterator.cc */; }; + 825403AC0D92D2CF0006B936 /* word_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = 8254038F0D92D2CF0006B936 /* word_iterator.h */; }; + 825403E10D92D31D0006B936 /* bitmap_header.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403C00D92D31D0006B936 /* bitmap_header.cc */; }; + 825403E20D92D31D0006B936 /* bitmap_header.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403C10D92D31D0006B936 /* bitmap_header.h */; }; + 825403E30D92D31D0006B936 /* bitmap_platform_device.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403C20D92D31D0006B936 /* bitmap_platform_device.cc */; }; + 825403E40D92D31D0006B936 /* bitmap_platform_device.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403C30D92D31D0006B936 /* bitmap_platform_device.h */; }; + 825403E50D92D31D0006B936 /* font_utils.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403C40D92D31D0006B936 /* font_utils.cc */; }; + 825403E60D92D31D0006B936 /* font_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403C50D92D31D0006B936 /* font_utils.h */; }; + 825403E70D92D31D0006B936 /* image_resizer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403C60D92D31D0006B936 /* image_resizer.cc */; }; + 825403E80D92D31D0006B936 /* image_resizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403C70D92D31D0006B936 /* image_resizer.h */; }; + 825403E90D92D31D0006B936 /* native_theme.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403C80D92D31D0006B936 /* native_theme.cc */; }; + 825403EA0D92D31D0006B936 /* native_theme.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403C90D92D31D0006B936 /* native_theme.h */; }; + 825403EB0D92D31D0006B936 /* platform_canvas.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403CA0D92D31D0006B936 /* platform_canvas.h */; }; + 825403EC0D92D31D0006B936 /* platform_device.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403CB0D92D31D0006B936 /* platform_device.cc */; }; + 825403ED0D92D31D0006B936 /* platform_device.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403CC0D92D31D0006B936 /* platform_device.h */; }; + 825403EE0D92D31D0006B936 /* png_decoder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403CD0D92D31D0006B936 /* png_decoder.cc */; }; + 825403EF0D92D31D0006B936 /* png_decoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403CE0D92D31D0006B936 /* png_decoder.h */; }; + 825403F00D92D31D0006B936 /* png_encoder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403CF0D92D31D0006B936 /* png_encoder.cc */; }; + 825403F10D92D31D0006B936 /* png_encoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403D00D92D31D0006B936 /* png_encoder.h */; }; + 825403F20D92D31D0006B936 /* point.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403D10D92D31D0006B936 /* point.cc */; }; + 825403F30D92D31D0006B936 /* point.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403D20D92D31D0006B936 /* point.h */; }; + 825403F40D92D31D0006B936 /* rect_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403D30D92D31D0006B936 /* rect_unittest.cc */; }; + 825403F50D92D31D0006B936 /* rect.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403D40D92D31D0006B936 /* rect.cc */; }; + 825403F60D92D31D0006B936 /* rect.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403D50D92D31D0006B936 /* rect.h */; }; + 825403F70D92D31D0006B936 /* size.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403D60D92D31D0006B936 /* size.cc */; }; + 825403F80D92D31D0006B936 /* size.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403D70D92D31D0006B936 /* size.h */; }; + 825403F90D92D31D0006B936 /* skia_utils.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403D80D92D31D0006B936 /* skia_utils.cc */; }; + 825403FA0D92D31D0006B936 /* skia_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403D90D92D31D0006B936 /* skia_utils.h */; }; + 825403FB0D92D31D0006B936 /* uniscribe.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403DA0D92D31D0006B936 /* uniscribe.cc */; }; + 825403FC0D92D31D0006B936 /* uniscribe.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403DB0D92D31D0006B936 /* uniscribe.h */; }; + 825403FD0D92D31D0006B936 /* vector_canvas.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403DC0D92D31D0006B936 /* vector_canvas.cc */; }; + 825403FE0D92D31D0006B936 /* vector_canvas.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403DD0D92D31D0006B936 /* vector_canvas.h */; }; + 825403FF0D92D31D0006B936 /* vector_device.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403DE0D92D31D0006B936 /* vector_device.cc */; }; + 825404000D92D31D0006B936 /* vector_device.h in Headers */ = {isa = PBXBuildFile; fileRef = 825403DF0D92D31D0006B936 /* vector_device.h */; }; + 825404010D92D31D0006B936 /* platform_canvas.cc in Sources */ = {isa = PBXBuildFile; fileRef = 825403E00D92D31D0006B936 /* platform_canvas.cc */; }; + 82E23FCD0D9C219600F8B40A /* platform_thread.h in Headers */ = {isa = PBXBuildFile; fileRef = 82E23FCB0D9C219600F8B40A /* platform_thread.h */; }; + 82E23FCE0D9C219600F8B40A /* platform_thread.cc in Sources */ = {isa = PBXBuildFile; fileRef = 82E23FCC0D9C219600F8B40A /* platform_thread.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 825404050D92D33A0006B936 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 825402AA0D92D0C60006B936 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 825402BA0D92D0FA0006B936; + remoteInfo = base; + }; + 825404070D92D33C0006B936 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 825402AA0D92D0C60006B936 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 825403B00D92D2E50006B936; + remoteInfo = base_gfx; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 7B5AD60D0D9DD8050012BCF1 /* scoped_cftyperef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scoped_cftyperef.h; sourceTree = "<group>"; }; + 7BD9E84E0DA447F800FC7A01 /* singleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = singleton.h; sourceTree = "<group>"; }; + 7BEB81100D9AD288009BA8DD /* prcpucfg_mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = prcpucfg_mac.h; path = third_party/nspr/prcpucfg_mac.h; sourceTree = "<group>"; }; + 7BEB81490D9B0F33009BA8DD /* time_mac.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = time_mac.cc; sourceTree = "<group>"; }; + 7BEB834D0D9C4BE0009BA8DD /* string_util_mac.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_util_mac.cc; sourceTree = "<group>"; }; + 7BEE52C10D9D84FD0067FF23 /* string_util_mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_util_mac.h; sourceTree = "<group>"; }; + 7BEFC29C0D99832D000829AD /* lock_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lock_impl.h; sourceTree = "<group>"; }; + 7BEFC29D0D99832D000829AD /* lock_impl_mac.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lock_impl_mac.cc; sourceTree = "<group>"; }; + 821B91680DAABD7F00F350D7 /* string16.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string16.h; sourceTree = "<group>"; }; + 825402BB0D92D0FA0006B936 /* libbase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libbase.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 825402C50D92D1390006B936 /* base_drag_source.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base_drag_source.cc; sourceTree = "<group>"; }; + 825402C60D92D1390006B936 /* base_drag_source.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base_drag_source.h; sourceTree = "<group>"; }; + 825402C70D92D1390006B936 /* base_drop_target.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base_drop_target.cc; sourceTree = "<group>"; }; + 825402C80D92D1390006B936 /* base_drop_target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base_drop_target.h; sourceTree = "<group>"; }; + 825402C90D92D1390006B936 /* base_paths.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base_paths.cc; sourceTree = "<group>"; }; + 825402CA0D92D1390006B936 /* base_paths.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base_paths.h; sourceTree = "<group>"; }; + 825402CB0D92D1390006B936 /* base_switches.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base_switches.cc; sourceTree = "<group>"; }; + 825402CC0D92D1390006B936 /* base_switches.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base_switches.h; sourceTree = "<group>"; }; + 825402CD0D92D1390006B936 /* basictypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = basictypes.h; sourceTree = "<group>"; }; + 825402D70D92D15E0006B936 /* blapi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = blapi.h; path = third_party/nss/blapi.h; sourceTree = "<group>"; }; + 825402D80D92D15E0006B936 /* blapit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = blapit.h; path = third_party/nss/blapit.h; sourceTree = "<group>"; }; + 825402DB0D92D1730006B936 /* clipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipboard.h; sourceTree = "<group>"; }; + 825402DC0D92D1730006B936 /* clipboard.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = clipboard.cc; sourceTree = "<group>"; }; + 825402DD0D92D1730006B936 /* clipboard_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipboard_util.h; sourceTree = "<group>"; }; + 825402DE0D92D1730006B936 /* clipboard_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = clipboard_util.cc; sourceTree = "<group>"; }; + 825402E30D92D1850006B936 /* command_line.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = command_line.cc; sourceTree = "<group>"; }; + 825402E40D92D1850006B936 /* command_line.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = command_line.h; sourceTree = "<group>"; }; + 825402EB0D92D1940006B936 /* condition_variable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = condition_variable.h; sourceTree = "<group>"; }; + 825402EC0D92D1940006B936 /* condition_variable.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = condition_variable.cc; sourceTree = "<group>"; }; + 825402ED0D92D1940006B936 /* condition_variable_events.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = condition_variable_events.h; sourceTree = "<group>"; }; + 825402F10D92D1AC0006B936 /* debug_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug_util.h; sourceTree = "<group>"; }; + 825402F20D92D1AC0006B936 /* debug_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = debug_util.cc; sourceTree = "<group>"; }; + 825402F30D92D1AC0006B936 /* debug_on_start.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug_on_start.h; sourceTree = "<group>"; }; + 825402F40D92D1AC0006B936 /* debug_on_start.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = debug_on_start.cc; sourceTree = "<group>"; }; + 825402FF0D92D1BC0006B936 /* event_recorder.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = event_recorder.cc; sourceTree = "<group>"; }; + 825403000D92D1BC0006B936 /* event_recorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = event_recorder.h; sourceTree = "<group>"; }; + 825403030D92D1C50006B936 /* file_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file_util.h; sourceTree = "<group>"; }; + 825403040D92D1C50006B936 /* file_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_util.cc; sourceTree = "<group>"; }; + 825403070D92D1CD0006B936 /* file_version_info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file_version_info.h; sourceTree = "<group>"; }; + 825403080D92D1CD0006B936 /* file_version_info.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_version_info.cc; sourceTree = "<group>"; }; + 8254030B0D92D1D10006B936 /* fix_wp64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fix_wp64.h; sourceTree = "<group>"; }; + 8254030D0D92D1DB0006B936 /* fixed_string.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixed_string.h; sourceTree = "<group>"; }; + 8254030F0D92D1E80006B936 /* iat_patch.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = iat_patch.cc; sourceTree = "<group>"; }; + 825403100D92D1E80006B936 /* iat_patch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iat_patch.h; sourceTree = "<group>"; }; + 825403110D92D1E80006B936 /* icu_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = icu_util.cc; sourceTree = "<group>"; }; + 825403120D92D1E80006B936 /* icu_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = icu_util.h; sourceTree = "<group>"; }; + 825403130D92D1E80006B936 /* id_map.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id_map.h; sourceTree = "<group>"; }; + 825403190D92D1F40006B936 /* image_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = image_util.cc; sourceTree = "<group>"; }; + 8254031A0D92D1F40006B936 /* image_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = image_util.h; sourceTree = "<group>"; }; + 8254031B0D92D1F40006B936 /* json_reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_reader.cc; sourceTree = "<group>"; }; + 8254031C0D92D1F40006B936 /* json_reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json_reader.h; sourceTree = "<group>"; }; + 8254031D0D92D1F40006B936 /* json_writer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_writer.cc; sourceTree = "<group>"; }; + 8254031E0D92D1F40006B936 /* json_writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json_writer.h; sourceTree = "<group>"; }; + 825403250D92D2090006B936 /* lock.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lock.cc; sourceTree = "<group>"; }; + 825403260D92D2090006B936 /* lock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lock.h; sourceTree = "<group>"; }; + 825403270D92D2090006B936 /* logging.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = logging.cc; sourceTree = "<group>"; }; + 825403280D92D2090006B936 /* logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = logging.h; sourceTree = "<group>"; }; + 825403290D92D2090006B936 /* md5.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = md5.cc; sourceTree = "<group>"; }; + 8254032A0D92D2090006B936 /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = "<group>"; }; + 8254032B0D92D2090006B936 /* memory_debug.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = memory_debug.cc; sourceTree = "<group>"; }; + 8254032C0D92D2090006B936 /* memory_debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = memory_debug.h; sourceTree = "<group>"; }; + 825403350D92D2110006B936 /* message_loop.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = message_loop.cc; sourceTree = "<group>"; }; + 825403360D92D2110006B936 /* message_loop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = message_loop.h; sourceTree = "<group>"; }; + 825403390D92D2210006B936 /* observer_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = observer_list.h; sourceTree = "<group>"; }; + 8254033A0D92D2210006B936 /* path_service.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = path_service.cc; sourceTree = "<group>"; }; + 8254033B0D92D2210006B936 /* path_service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = path_service.h; sourceTree = "<group>"; }; + 8254033C0D92D2210006B936 /* pe_image.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pe_image.cc; sourceTree = "<group>"; }; + 8254033D0D92D2210006B936 /* pe_image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pe_image.h; sourceTree = "<group>"; }; + 8254033E0D92D2210006B936 /* pickle.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pickle.cc; sourceTree = "<group>"; }; + 8254033F0D92D2210006B936 /* pickle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pickle.h; sourceTree = "<group>"; }; + 825403400D92D2210006B936 /* port.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = port.h; sourceTree = "<group>"; }; + 825403490D92D23C0006B936 /* prtypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = prtypes.h; path = third_party/nspr/prtypes.h; sourceTree = "<group>"; }; + 8254034A0D92D23C0006B936 /* prtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = prtime.h; path = third_party/nspr/prtime.h; sourceTree = "<group>"; }; + 8254034B0D92D23C0006B936 /* prtime.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = prtime.cc; path = third_party/nspr/prtime.cc; sourceTree = "<group>"; }; + 8254034C0D92D23C0006B936 /* prcpucfg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = prcpucfg.h; path = third_party/nspr/prcpucfg.h; sourceTree = "<group>"; }; + 825403510D92D24D0006B936 /* process_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = process_util.h; sourceTree = "<group>"; }; + 825403520D92D24D0006B936 /* process_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = process_util.cc; sourceTree = "<group>"; }; + 825403550D92D2580006B936 /* pure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pure.h; path = third_party/purify/pure.h; sourceTree = "<group>"; }; + 825403570D92D25E0006B936 /* pure_api.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = pure_api.c; path = third_party/purify/pure_api.c; sourceTree = "<group>"; }; + 825403590D92D27C0006B936 /* ref_counted.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ref_counted.h; sourceTree = "<group>"; }; + 8254035A0D92D27C0006B936 /* registry.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = registry.cc; sourceTree = "<group>"; }; + 8254035B0D92D27C0006B936 /* registry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = registry.h; sourceTree = "<group>"; }; + 8254035C0D92D27C0006B936 /* resource_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = resource_util.cc; sourceTree = "<group>"; }; + 8254035D0D92D27C0006B936 /* resource_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = resource_util.h; sourceTree = "<group>"; }; + 8254035E0D92D27C0006B936 /* revocable_store.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = revocable_store.cc; sourceTree = "<group>"; }; + 8254035F0D92D27C0006B936 /* revocable_store.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = revocable_store.h; sourceTree = "<group>"; }; + 825403610D92D27C0006B936 /* scoped_ptr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scoped_ptr.h; sourceTree = "<group>"; }; + 825403620D92D27C0006B936 /* sha2.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sha2.cc; sourceTree = "<group>"; }; + 825403630D92D27C0006B936 /* sha2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha2.h; sourceTree = "<group>"; }; + 8254036F0D92D2840006B936 /* sha256.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sha256.h; path = third_party/nss/sha256.h; sourceTree = "<group>"; }; + 825403700D92D2840006B936 /* sha512.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sha512.cc; path = third_party/nss/sha512.cc; sourceTree = "<group>"; }; + 825403730D92D2CF0006B936 /* shared_event.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shared_event.cc; sourceTree = "<group>"; }; + 825403740D92D2CF0006B936 /* shared_event.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shared_event.h; sourceTree = "<group>"; }; + 825403750D92D2CF0006B936 /* shared_memory.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shared_memory.cc; sourceTree = "<group>"; }; + 825403760D92D2CF0006B936 /* shared_memory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shared_memory.h; sourceTree = "<group>"; }; + 825403770D92D2CF0006B936 /* stack_container.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stack_container.h; sourceTree = "<group>"; }; + 825403780D92D2CF0006B936 /* stats_counters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stats_counters.h; sourceTree = "<group>"; }; + 825403790D92D2CF0006B936 /* stats_table.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stats_table.cc; sourceTree = "<group>"; }; + 8254037A0D92D2CF0006B936 /* stats_table.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stats_table.h; sourceTree = "<group>"; }; + 8254037B0D92D2CF0006B936 /* string_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_tokenizer.h; sourceTree = "<group>"; }; + 8254037C0D92D2CF0006B936 /* string_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_util.cc; sourceTree = "<group>"; }; + 8254037D0D92D2CF0006B936 /* string_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_util.h; sourceTree = "<group>"; }; + 8254037E0D92D2CF0006B936 /* task.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = task.h; sourceTree = "<group>"; }; + 8254037F0D92D2CF0006B936 /* thread.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = thread.cc; sourceTree = "<group>"; }; + 825403800D92D2CF0006B936 /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread.h; sourceTree = "<group>"; }; + 825403810D92D2CF0006B936 /* thread_local_storage.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = thread_local_storage.cc; sourceTree = "<group>"; }; + 825403820D92D2CF0006B936 /* thread_local_storage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread_local_storage.h; sourceTree = "<group>"; }; + 825403830D92D2CF0006B936 /* time.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = time.cc; sourceTree = "<group>"; }; + 825403840D92D2CF0006B936 /* time.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = time.h; sourceTree = "<group>"; }; + 825403850D92D2CF0006B936 /* timer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = timer.cc; sourceTree = "<group>"; }; + 825403860D92D2CF0006B936 /* timer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timer.h; sourceTree = "<group>"; }; + 825403870D92D2CF0006B936 /* tuple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tuple.h; sourceTree = "<group>"; }; + 825403880D92D2CF0006B936 /* values.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = values.cc; sourceTree = "<group>"; }; + 825403890D92D2CF0006B936 /* values.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = values.h; sourceTree = "<group>"; }; + 8254038A0D92D2CF0006B936 /* win_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = win_util.cc; sourceTree = "<group>"; }; + 8254038B0D92D2CF0006B936 /* win_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = win_util.h; sourceTree = "<group>"; }; + 8254038C0D92D2CF0006B936 /* wmi_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wmi_util.cc; sourceTree = "<group>"; }; + 8254038D0D92D2CF0006B936 /* wmi_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wmi_util.h; sourceTree = "<group>"; }; + 8254038E0D92D2CF0006B936 /* word_iterator.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = word_iterator.cc; sourceTree = "<group>"; }; + 8254038F0D92D2CF0006B936 /* word_iterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = word_iterator.h; sourceTree = "<group>"; }; + 825403B10D92D2E50006B936 /* libbase_gfx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libbase_gfx.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 825403C00D92D31D0006B936 /* bitmap_header.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitmap_header.cc; sourceTree = "<group>"; }; + 825403C10D92D31D0006B936 /* bitmap_header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitmap_header.h; sourceTree = "<group>"; }; + 825403C20D92D31D0006B936 /* bitmap_platform_device.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitmap_platform_device.cc; sourceTree = "<group>"; }; + 825403C30D92D31D0006B936 /* bitmap_platform_device.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitmap_platform_device.h; sourceTree = "<group>"; }; + 825403C40D92D31D0006B936 /* font_utils.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = font_utils.cc; sourceTree = "<group>"; }; + 825403C50D92D31D0006B936 /* font_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = font_utils.h; sourceTree = "<group>"; }; + 825403C60D92D31D0006B936 /* image_resizer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = image_resizer.cc; sourceTree = "<group>"; }; + 825403C70D92D31D0006B936 /* image_resizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = image_resizer.h; sourceTree = "<group>"; }; + 825403C80D92D31D0006B936 /* native_theme.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = native_theme.cc; sourceTree = "<group>"; }; + 825403C90D92D31D0006B936 /* native_theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = native_theme.h; sourceTree = "<group>"; }; + 825403CA0D92D31D0006B936 /* platform_canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform_canvas.h; sourceTree = "<group>"; }; + 825403CB0D92D31D0006B936 /* platform_device.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = platform_device.cc; sourceTree = "<group>"; }; + 825403CC0D92D31D0006B936 /* platform_device.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform_device.h; sourceTree = "<group>"; }; + 825403CD0D92D31D0006B936 /* png_decoder.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = png_decoder.cc; sourceTree = "<group>"; }; + 825403CE0D92D31D0006B936 /* png_decoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = png_decoder.h; sourceTree = "<group>"; }; + 825403CF0D92D31D0006B936 /* png_encoder.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = png_encoder.cc; sourceTree = "<group>"; }; + 825403D00D92D31D0006B936 /* png_encoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = png_encoder.h; sourceTree = "<group>"; }; + 825403D10D92D31D0006B936 /* point.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = point.cc; sourceTree = "<group>"; }; + 825403D20D92D31D0006B936 /* point.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = point.h; sourceTree = "<group>"; }; + 825403D30D92D31D0006B936 /* rect_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rect_unittest.cc; sourceTree = "<group>"; }; + 825403D40D92D31D0006B936 /* rect.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rect.cc; sourceTree = "<group>"; }; + 825403D50D92D31D0006B936 /* rect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rect.h; sourceTree = "<group>"; }; + 825403D60D92D31D0006B936 /* size.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = size.cc; sourceTree = "<group>"; }; + 825403D70D92D31D0006B936 /* size.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = size.h; sourceTree = "<group>"; }; + 825403D80D92D31D0006B936 /* skia_utils.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = skia_utils.cc; sourceTree = "<group>"; }; + 825403D90D92D31D0006B936 /* skia_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = skia_utils.h; sourceTree = "<group>"; }; + 825403DA0D92D31D0006B936 /* uniscribe.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uniscribe.cc; sourceTree = "<group>"; }; + 825403DB0D92D31D0006B936 /* uniscribe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uniscribe.h; sourceTree = "<group>"; }; + 825403DC0D92D31D0006B936 /* vector_canvas.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = vector_canvas.cc; sourceTree = "<group>"; }; + 825403DD0D92D31D0006B936 /* vector_canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector_canvas.h; sourceTree = "<group>"; }; + 825403DE0D92D31D0006B936 /* vector_device.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = vector_device.cc; sourceTree = "<group>"; }; + 825403DF0D92D31D0006B936 /* vector_device.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector_device.h; sourceTree = "<group>"; }; + 825403E00D92D31D0006B936 /* platform_canvas.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = platform_canvas.cc; sourceTree = "<group>"; }; + 82E23FCB0D9C219600F8B40A /* platform_thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform_thread.h; sourceTree = "<group>"; }; + 82E23FCC0D9C219600F8B40A /* platform_thread.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = platform_thread.cc; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 825402B90D92D0FA0006B936 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 825403AF0D92D2E50006B936 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 825402A80D92D0C60006B936 = { + isa = PBXGroup; + children = ( + 825402B60D92D0E20006B936 /* base */, + 825403B40D92D2EC0006B936 /* base_gfx */, + 825402BC0D92D0FA0006B936 /* Products */, + ); + sourceTree = "<group>"; + }; + 825402B60D92D0E20006B936 /* base */ = { + isa = PBXGroup; + children = ( + 825402C50D92D1390006B936 /* base_drag_source.cc */, + 825402C60D92D1390006B936 /* base_drag_source.h */, + 825402C70D92D1390006B936 /* base_drop_target.cc */, + 825402C80D92D1390006B936 /* base_drop_target.h */, + 825402C90D92D1390006B936 /* base_paths.cc */, + 825402CA0D92D1390006B936 /* base_paths.h */, + 825402CB0D92D1390006B936 /* base_switches.cc */, + 825402CC0D92D1390006B936 /* base_switches.h */, + 825402CD0D92D1390006B936 /* basictypes.h */, + 825402D70D92D15E0006B936 /* blapi.h */, + 825402D80D92D15E0006B936 /* blapit.h */, + 825402DB0D92D1730006B936 /* clipboard.h */, + 825402DC0D92D1730006B936 /* clipboard.cc */, + 825402DD0D92D1730006B936 /* clipboard_util.h */, + 825402DE0D92D1730006B936 /* clipboard_util.cc */, + 825402E40D92D1850006B936 /* command_line.h */, + 825402E30D92D1850006B936 /* command_line.cc */, + 825402EB0D92D1940006B936 /* condition_variable.h */, + 825402EC0D92D1940006B936 /* condition_variable.cc */, + 825402ED0D92D1940006B936 /* condition_variable_events.h */, + 825402F30D92D1AC0006B936 /* debug_on_start.h */, + 825402F40D92D1AC0006B936 /* debug_on_start.cc */, + 825402F10D92D1AC0006B936 /* debug_util.h */, + 825402F20D92D1AC0006B936 /* debug_util.cc */, + 825402FF0D92D1BC0006B936 /* event_recorder.cc */, + 825403000D92D1BC0006B936 /* event_recorder.h */, + 825403030D92D1C50006B936 /* file_util.h */, + 825403040D92D1C50006B936 /* file_util.cc */, + 825403070D92D1CD0006B936 /* file_version_info.h */, + 825403080D92D1CD0006B936 /* file_version_info.cc */, + 8254030B0D92D1D10006B936 /* fix_wp64.h */, + 8254030D0D92D1DB0006B936 /* fixed_string.h */, + 8254030F0D92D1E80006B936 /* iat_patch.cc */, + 825403100D92D1E80006B936 /* iat_patch.h */, + 825403110D92D1E80006B936 /* icu_util.cc */, + 825403120D92D1E80006B936 /* icu_util.h */, + 825403130D92D1E80006B936 /* id_map.h */, + 825403190D92D1F40006B936 /* image_util.cc */, + 8254031A0D92D1F40006B936 /* image_util.h */, + 8254031B0D92D1F40006B936 /* json_reader.cc */, + 8254031C0D92D1F40006B936 /* json_reader.h */, + 8254031D0D92D1F40006B936 /* json_writer.cc */, + 8254031E0D92D1F40006B936 /* json_writer.h */, + 825403250D92D2090006B936 /* lock.cc */, + 825403260D92D2090006B936 /* lock.h */, + 7BEFC29C0D99832D000829AD /* lock_impl.h */, + 7BEFC29D0D99832D000829AD /* lock_impl_mac.cc */, + 825403270D92D2090006B936 /* logging.cc */, + 825403280D92D2090006B936 /* logging.h */, + 825403290D92D2090006B936 /* md5.cc */, + 8254032A0D92D2090006B936 /* md5.h */, + 8254032B0D92D2090006B936 /* memory_debug.cc */, + 8254032C0D92D2090006B936 /* memory_debug.h */, + 825403350D92D2110006B936 /* message_loop.cc */, + 825403360D92D2110006B936 /* message_loop.h */, + 825403390D92D2210006B936 /* observer_list.h */, + 8254033A0D92D2210006B936 /* path_service.cc */, + 8254033B0D92D2210006B936 /* path_service.h */, + 8254033C0D92D2210006B936 /* pe_image.cc */, + 8254033D0D92D2210006B936 /* pe_image.h */, + 8254033E0D92D2210006B936 /* pickle.cc */, + 8254033F0D92D2210006B936 /* pickle.h */, + 82E23FCB0D9C219600F8B40A /* platform_thread.h */, + 82E23FCC0D9C219600F8B40A /* platform_thread.cc */, + 825403400D92D2210006B936 /* port.h */, + 825403490D92D23C0006B936 /* prtypes.h */, + 825403510D92D24D0006B936 /* process_util.h */, + 825403520D92D24D0006B936 /* process_util.cc */, + 8254034A0D92D23C0006B936 /* prtime.h */, + 8254034B0D92D23C0006B936 /* prtime.cc */, + 8254034C0D92D23C0006B936 /* prcpucfg.h */, + 7BEB81100D9AD288009BA8DD /* prcpucfg_mac.h */, + 825403550D92D2580006B936 /* pure.h */, + 825403570D92D25E0006B936 /* pure_api.c */, + 825403590D92D27C0006B936 /* ref_counted.h */, + 8254035A0D92D27C0006B936 /* registry.cc */, + 8254035B0D92D27C0006B936 /* registry.h */, + 8254035C0D92D27C0006B936 /* resource_util.cc */, + 8254035D0D92D27C0006B936 /* resource_util.h */, + 8254035E0D92D27C0006B936 /* revocable_store.cc */, + 8254035F0D92D27C0006B936 /* revocable_store.h */, + 7B5AD60D0D9DD8050012BCF1 /* scoped_cftyperef.h */, + 825403610D92D27C0006B936 /* scoped_ptr.h */, + 825403620D92D27C0006B936 /* sha2.cc */, + 825403630D92D27C0006B936 /* sha2.h */, + 8254036F0D92D2840006B936 /* sha256.h */, + 825403700D92D2840006B936 /* sha512.cc */, + 825403730D92D2CF0006B936 /* shared_event.cc */, + 825403740D92D2CF0006B936 /* shared_event.h */, + 825403750D92D2CF0006B936 /* shared_memory.cc */, + 825403760D92D2CF0006B936 /* shared_memory.h */, + 7BD9E84E0DA447F800FC7A01 /* singleton.h */, + 825403770D92D2CF0006B936 /* stack_container.h */, + 825403780D92D2CF0006B936 /* stats_counters.h */, + 825403790D92D2CF0006B936 /* stats_table.cc */, + 8254037A0D92D2CF0006B936 /* stats_table.h */, + 8254037B0D92D2CF0006B936 /* string_tokenizer.h */, + 8254037C0D92D2CF0006B936 /* string_util.cc */, + 8254037D0D92D2CF0006B936 /* string_util.h */, + 7BEB834D0D9C4BE0009BA8DD /* string_util_mac.cc */, + 7BEE52C10D9D84FD0067FF23 /* string_util_mac.h */, + 821B91680DAABD7F00F350D7 /* string16.h */, + 8254037E0D92D2CF0006B936 /* task.h */, + 8254037F0D92D2CF0006B936 /* thread.cc */, + 825403800D92D2CF0006B936 /* thread.h */, + 825403810D92D2CF0006B936 /* thread_local_storage.cc */, + 825403820D92D2CF0006B936 /* thread_local_storage.h */, + 825403830D92D2CF0006B936 /* time.cc */, + 825403840D92D2CF0006B936 /* time.h */, + 7BEB81490D9B0F33009BA8DD /* time_mac.cc */, + 825403850D92D2CF0006B936 /* timer.cc */, + 825403860D92D2CF0006B936 /* timer.h */, + 825403870D92D2CF0006B936 /* tuple.h */, + 825403880D92D2CF0006B936 /* values.cc */, + 825403890D92D2CF0006B936 /* values.h */, + 8254038A0D92D2CF0006B936 /* win_util.cc */, + 8254038B0D92D2CF0006B936 /* win_util.h */, + 8254038C0D92D2CF0006B936 /* wmi_util.cc */, + 8254038D0D92D2CF0006B936 /* wmi_util.h */, + 8254038E0D92D2CF0006B936 /* word_iterator.cc */, + 8254038F0D92D2CF0006B936 /* word_iterator.h */, + ); + name = base; + sourceTree = "<group>"; + }; + 825402BC0D92D0FA0006B936 /* Products */ = { + isa = PBXGroup; + children = ( + 825402BB0D92D0FA0006B936 /* libbase.a */, + 825403B10D92D2E50006B936 /* libbase_gfx.a */, + ); + name = Products; + sourceTree = "<group>"; + }; + 825403B40D92D2EC0006B936 /* base_gfx */ = { + isa = PBXGroup; + children = ( + 825403C00D92D31D0006B936 /* bitmap_header.cc */, + 825403C10D92D31D0006B936 /* bitmap_header.h */, + 825403C20D92D31D0006B936 /* bitmap_platform_device.cc */, + 825403C30D92D31D0006B936 /* bitmap_platform_device.h */, + 825403C40D92D31D0006B936 /* font_utils.cc */, + 825403C50D92D31D0006B936 /* font_utils.h */, + 825403C60D92D31D0006B936 /* image_resizer.cc */, + 825403C70D92D31D0006B936 /* image_resizer.h */, + 825403C80D92D31D0006B936 /* native_theme.cc */, + 825403C90D92D31D0006B936 /* native_theme.h */, + 825403CA0D92D31D0006B936 /* platform_canvas.h */, + 825403CB0D92D31D0006B936 /* platform_device.cc */, + 825403CC0D92D31D0006B936 /* platform_device.h */, + 825403CD0D92D31D0006B936 /* png_decoder.cc */, + 825403CE0D92D31D0006B936 /* png_decoder.h */, + 825403CF0D92D31D0006B936 /* png_encoder.cc */, + 825403D00D92D31D0006B936 /* png_encoder.h */, + 825403D10D92D31D0006B936 /* point.cc */, + 825403D20D92D31D0006B936 /* point.h */, + 825403D30D92D31D0006B936 /* rect_unittest.cc */, + 825403D40D92D31D0006B936 /* rect.cc */, + 825403D50D92D31D0006B936 /* rect.h */, + 825403D60D92D31D0006B936 /* size.cc */, + 825403D70D92D31D0006B936 /* size.h */, + 825403D80D92D31D0006B936 /* skia_utils.cc */, + 825403D90D92D31D0006B936 /* skia_utils.h */, + 825403DA0D92D31D0006B936 /* uniscribe.cc */, + 825403DB0D92D31D0006B936 /* uniscribe.h */, + 825403DC0D92D31D0006B936 /* vector_canvas.cc */, + 825403DD0D92D31D0006B936 /* vector_canvas.h */, + 825403DE0D92D31D0006B936 /* vector_device.cc */, + 825403DF0D92D31D0006B936 /* vector_device.h */, + 825403E00D92D31D0006B936 /* platform_canvas.cc */, + ); + name = base_gfx; + path = gfx; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 825402B70D92D0FA0006B936 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 825402CF0D92D1390006B936 /* base_drag_source.h in Headers */, + 825402D10D92D1390006B936 /* base_drop_target.h in Headers */, + 825402D30D92D1390006B936 /* base_paths.h in Headers */, + 825402D50D92D1390006B936 /* base_switches.h in Headers */, + 825402D60D92D1390006B936 /* basictypes.h in Headers */, + 825402D90D92D15E0006B936 /* blapi.h in Headers */, + 825402DA0D92D15E0006B936 /* blapit.h in Headers */, + 825402DF0D92D1730006B936 /* clipboard.h in Headers */, + 825402E10D92D1730006B936 /* clipboard_util.h in Headers */, + 825402E60D92D1850006B936 /* command_line.h in Headers */, + 825402EE0D92D1940006B936 /* condition_variable.h in Headers */, + 825402F00D92D1940006B936 /* condition_variable_events.h in Headers */, + 825402F50D92D1AC0006B936 /* debug_util.h in Headers */, + 825402F70D92D1AC0006B936 /* debug_on_start.h in Headers */, + 825403020D92D1BC0006B936 /* event_recorder.h in Headers */, + 825403050D92D1C50006B936 /* file_util.h in Headers */, + 825403090D92D1CD0006B936 /* file_version_info.h in Headers */, + 8254030C0D92D1D10006B936 /* fix_wp64.h in Headers */, + 8254030E0D92D1DB0006B936 /* fixed_string.h in Headers */, + 825403150D92D1E80006B936 /* iat_patch.h in Headers */, + 825403170D92D1E80006B936 /* icu_util.h in Headers */, + 825403180D92D1E80006B936 /* id_map.h in Headers */, + 825403200D92D1F40006B936 /* image_util.h in Headers */, + 825403220D92D1F40006B936 /* json_reader.h in Headers */, + 825403240D92D1F40006B936 /* json_writer.h in Headers */, + 8254032E0D92D2090006B936 /* lock.h in Headers */, + 7BEFC29E0D99832D000829AD /* lock_impl.h in Headers */, + 825403300D92D2090006B936 /* logging.h in Headers */, + 825403320D92D2090006B936 /* md5.h in Headers */, + 825403340D92D2090006B936 /* memory_debug.h in Headers */, + 825403380D92D2110006B936 /* message_loop.h in Headers */, + 825403410D92D2210006B936 /* observer_list.h in Headers */, + 825403430D92D2210006B936 /* path_service.h in Headers */, + 825403450D92D2210006B936 /* pe_image.h in Headers */, + 825403470D92D2210006B936 /* pickle.h in Headers */, + 82E23FCD0D9C219600F8B40A /* platform_thread.h in Headers */, + 825403480D92D2210006B936 /* port.h in Headers */, + 8254034D0D92D23C0006B936 /* prtypes.h in Headers */, + 8254034E0D92D23C0006B936 /* prtime.h in Headers */, + 825403500D92D23C0006B936 /* prcpucfg.h in Headers */, + 7BEB81110D9AD288009BA8DD /* prcpucfg_mac.h in Headers */, + 825403530D92D24D0006B936 /* process_util.h in Headers */, + 825403560D92D2580006B936 /* pure.h in Headers */, + 825403640D92D27C0006B936 /* ref_counted.h in Headers */, + 825403660D92D27C0006B936 /* registry.h in Headers */, + 825403680D92D27C0006B936 /* resource_util.h in Headers */, + 8254036A0D92D27C0006B936 /* revocable_store.h in Headers */, + 7B5AD60E0D9DD8050012BCF1 /* scoped_cftyperef.h in Headers */, + 8254036C0D92D27C0006B936 /* scoped_ptr.h in Headers */, + 8254036E0D92D27C0006B936 /* sha2.h in Headers */, + 825403710D92D2840006B936 /* sha256.h in Headers */, + 825403910D92D2CF0006B936 /* shared_event.h in Headers */, + 825403930D92D2CF0006B936 /* shared_memory.h in Headers */, + 7BD9E84F0DA447F800FC7A01 /* singleton.h in Headers */, + 825403940D92D2CF0006B936 /* stack_container.h in Headers */, + 825403950D92D2CF0006B936 /* stats_counters.h in Headers */, + 825403970D92D2CF0006B936 /* stats_table.h in Headers */, + 825403980D92D2CF0006B936 /* string_tokenizer.h in Headers */, + 8254039A0D92D2CF0006B936 /* string_util.h in Headers */, + 7BEE52C20D9D84FD0067FF23 /* string_util_mac.h in Headers */, + 821B91690DAABD7F00F350D7 /* string16.h in Headers */, + 8254039B0D92D2CF0006B936 /* task.h in Headers */, + 8254039D0D92D2CF0006B936 /* thread.h in Headers */, + 8254039F0D92D2CF0006B936 /* thread_local_storage.h in Headers */, + 825403A10D92D2CF0006B936 /* time.h in Headers */, + 825403A30D92D2CF0006B936 /* timer.h in Headers */, + 825403A40D92D2CF0006B936 /* tuple.h in Headers */, + 825403A60D92D2CF0006B936 /* values.h in Headers */, + 825403A80D92D2CF0006B936 /* win_util.h in Headers */, + 825403AA0D92D2CF0006B936 /* wmi_util.h in Headers */, + 825403AC0D92D2CF0006B936 /* word_iterator.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 825403AD0D92D2E50006B936 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 825403E20D92D31D0006B936 /* bitmap_header.h in Headers */, + 825403E40D92D31D0006B936 /* bitmap_platform_device.h in Headers */, + 825403E60D92D31D0006B936 /* font_utils.h in Headers */, + 825403E80D92D31D0006B936 /* image_resizer.h in Headers */, + 825403EA0D92D31D0006B936 /* native_theme.h in Headers */, + 825403EB0D92D31D0006B936 /* platform_canvas.h in Headers */, + 825403ED0D92D31D0006B936 /* platform_device.h in Headers */, + 825403EF0D92D31D0006B936 /* png_decoder.h in Headers */, + 825403F10D92D31D0006B936 /* png_encoder.h in Headers */, + 825403F30D92D31D0006B936 /* point.h in Headers */, + 825403F60D92D31D0006B936 /* rect.h in Headers */, + 825403F80D92D31D0006B936 /* size.h in Headers */, + 825403FA0D92D31D0006B936 /* skia_utils.h in Headers */, + 825403FC0D92D31D0006B936 /* uniscribe.h in Headers */, + 825403FE0D92D31D0006B936 /* vector_canvas.h in Headers */, + 825404000D92D31D0006B936 /* vector_device.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 825402BA0D92D0FA0006B936 /* base */ = { + isa = PBXNativeTarget; + buildConfigurationList = 825402BF0D92D0FB0006B936 /* Build configuration list for PBXNativeTarget "base" */; + buildPhases = ( + 825402B70D92D0FA0006B936 /* Headers */, + 825402B80D92D0FA0006B936 /* Sources */, + 825402B90D92D0FA0006B936 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = base; + productName = base; + productReference = 825402BB0D92D0FA0006B936 /* libbase.a */; + productType = "com.apple.product-type.library.static"; + }; + 825403B00D92D2E50006B936 /* base_gfx */ = { + isa = PBXNativeTarget; + buildConfigurationList = 825403B50D92D2EC0006B936 /* Build configuration list for PBXNativeTarget "base_gfx" */; + buildPhases = ( + 825403AD0D92D2E50006B936 /* Headers */, + 825403AE0D92D2E50006B936 /* Sources */, + 825403AF0D92D2E50006B936 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = base_gfx; + productName = base_gfx; + productReference = 825403B10D92D2E50006B936 /* libbase_gfx.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 825402AA0D92D0C60006B936 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 825402AD0D92D0C60006B936 /* Build configuration list for PBXProject "base" */; + compatibilityVersion = "Xcode 2.4"; + hasScannedForEncodings = 0; + mainGroup = 825402A80D92D0C60006B936; + productRefGroup = 825402BC0D92D0FA0006B936 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 825402BA0D92D0FA0006B936 /* base */, + 825403B00D92D2E50006B936 /* base_gfx */, + 825404020D92D3340006B936 /* All */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 825402B80D92D0FA0006B936 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 825402CE0D92D1390006B936 /* base_drag_source.cc in Sources */, + 825402D00D92D1390006B936 /* base_drop_target.cc in Sources */, + 825402D20D92D1390006B936 /* base_paths.cc in Sources */, + 825402D40D92D1390006B936 /* base_switches.cc in Sources */, + 825402E00D92D1730006B936 /* clipboard.cc in Sources */, + 825402E20D92D1730006B936 /* clipboard_util.cc in Sources */, + 825402E50D92D1850006B936 /* command_line.cc in Sources */, + 825402EF0D92D1940006B936 /* condition_variable.cc in Sources */, + 825402F60D92D1AC0006B936 /* debug_util.cc in Sources */, + 825402F80D92D1AC0006B936 /* debug_on_start.cc in Sources */, + 825403010D92D1BC0006B936 /* event_recorder.cc in Sources */, + 825403060D92D1C50006B936 /* file_util.cc in Sources */, + 8254030A0D92D1CD0006B936 /* file_version_info.cc in Sources */, + 825403140D92D1E80006B936 /* iat_patch.cc in Sources */, + 825403160D92D1E80006B936 /* icu_util.cc in Sources */, + 8254031F0D92D1F40006B936 /* image_util.cc in Sources */, + 825403210D92D1F40006B936 /* json_reader.cc in Sources */, + 825403230D92D1F40006B936 /* json_writer.cc in Sources */, + 8254032D0D92D2090006B936 /* lock.cc in Sources */, + 7BEFC29F0D99832D000829AD /* lock_impl_mac.cc in Sources */, + 8254032F0D92D2090006B936 /* logging.cc in Sources */, + 825403310D92D2090006B936 /* md5.cc in Sources */, + 825403330D92D2090006B936 /* memory_debug.cc in Sources */, + 825403370D92D2110006B936 /* message_loop.cc in Sources */, + 825403420D92D2210006B936 /* path_service.cc in Sources */, + 825403440D92D2210006B936 /* pe_image.cc in Sources */, + 825403460D92D2210006B936 /* pickle.cc in Sources */, + 82E23FCE0D9C219600F8B40A /* platform_thread.cc in Sources */, + 8254034F0D92D23C0006B936 /* prtime.cc in Sources */, + 825403540D92D24D0006B936 /* process_util.cc in Sources */, + 825403580D92D25E0006B936 /* pure_api.c in Sources */, + 825403650D92D27C0006B936 /* registry.cc in Sources */, + 825403670D92D27C0006B936 /* resource_util.cc in Sources */, + 825403690D92D27C0006B936 /* revocable_store.cc in Sources */, + 8254036D0D92D27C0006B936 /* sha2.cc in Sources */, + 825403720D92D2840006B936 /* sha512.cc in Sources */, + 825403900D92D2CF0006B936 /* shared_event.cc in Sources */, + 825403920D92D2CF0006B936 /* shared_memory.cc in Sources */, + 825403960D92D2CF0006B936 /* stats_table.cc in Sources */, + 825403990D92D2CF0006B936 /* string_util.cc in Sources */, + 7BEB834E0D9C4BE0009BA8DD /* string_util_mac.cc in Sources */, + 8254039C0D92D2CF0006B936 /* thread.cc in Sources */, + 8254039E0D92D2CF0006B936 /* thread_local_storage.cc in Sources */, + 825403A00D92D2CF0006B936 /* time.cc in Sources */, + 7BEB814A0D9B0F33009BA8DD /* time_mac.cc in Sources */, + 825403A20D92D2CF0006B936 /* timer.cc in Sources */, + 825403A50D92D2CF0006B936 /* values.cc in Sources */, + 825403A70D92D2CF0006B936 /* win_util.cc in Sources */, + 825403A90D92D2CF0006B936 /* wmi_util.cc in Sources */, + 825403AB0D92D2CF0006B936 /* word_iterator.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 825403AE0D92D2E50006B936 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 825403E10D92D31D0006B936 /* bitmap_header.cc in Sources */, + 825403E30D92D31D0006B936 /* bitmap_platform_device.cc in Sources */, + 825403E50D92D31D0006B936 /* font_utils.cc in Sources */, + 825403E70D92D31D0006B936 /* image_resizer.cc in Sources */, + 825403E90D92D31D0006B936 /* native_theme.cc in Sources */, + 825403EC0D92D31D0006B936 /* platform_device.cc in Sources */, + 825403EE0D92D31D0006B936 /* png_decoder.cc in Sources */, + 825403F00D92D31D0006B936 /* png_encoder.cc in Sources */, + 825403F20D92D31D0006B936 /* point.cc in Sources */, + 825403F40D92D31D0006B936 /* rect_unittest.cc in Sources */, + 825403F50D92D31D0006B936 /* rect.cc in Sources */, + 825403F70D92D31D0006B936 /* size.cc in Sources */, + 825403F90D92D31D0006B936 /* skia_utils.cc in Sources */, + 825403FB0D92D31D0006B936 /* uniscribe.cc in Sources */, + 825403FD0D92D31D0006B936 /* vector_canvas.cc in Sources */, + 825403FF0D92D31D0006B936 /* vector_device.cc in Sources */, + 825404010D92D31D0006B936 /* platform_canvas.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 825404060D92D33A0006B936 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 825402BA0D92D0FA0006B936 /* base */; + targetProxy = 825404050D92D33A0006B936 /* PBXContainerItemProxy */; + }; + 825404080D92D33C0006B936 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 825403B00D92D2E50006B936 /* base_gfx */; + targetProxy = 825404070D92D33C0006B936 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 825402AB0D92D0C60006B936 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_CW_ASM_SYNTAX = NO; + GCC_ENABLE_PASCAL_STRINGS = NO; + GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + USE_HEADERMAP = NO; + WARNING_CFLAGS = "-Wall"; + }; + name = Debug; + }; + 825402AC0D92D0C60006B936 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_CW_ASM_SYNTAX = NO; + GCC_ENABLE_PASCAL_STRINGS = NO; + GCC_PREPROCESSOR_DEFINITIONS = NDEBUG; + USE_HEADERMAP = NO; + WARNING_CFLAGS = "-Wall"; + }; + name = Release; + }; + 825402BD0D92D0FB0006B936 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + HEADER_SEARCH_PATHS = ( + .., + ../third_party/icu38/public/common, + ../third_party/icu38/public/i18n, + ); + PREBINDING = NO; + PRODUCT_NAME = base; + }; + name = Debug; + }; + 825402BE0D92D0FB0006B936 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_DYNAMIC_NO_PIC = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + HEADER_SEARCH_PATHS = ( + .., + ../third_party/icu38/public/common, + ../third_party/icu38/public/i18n, + ); + PREBINDING = NO; + PRODUCT_NAME = base; + }; + name = Release; + }; + 825403B20D92D2E50006B936 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + PREBINDING = NO; + PRODUCT_NAME = base_gfx; + }; + name = Debug; + }; + 825403B30D92D2E50006B936 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_DYNAMIC_NO_PIC = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + PREBINDING = NO; + PRODUCT_NAME = base_gfx; + }; + name = Release; + }; + 825404030D92D3340006B936 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = All; + }; + name = Debug; + }; + 825404040D92D3340006B936 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = All; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 825402AD0D92D0C60006B936 /* Build configuration list for PBXProject "base" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 825402AB0D92D0C60006B936 /* Debug */, + 825402AC0D92D0C60006B936 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 825402BF0D92D0FB0006B936 /* Build configuration list for PBXNativeTarget "base" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 825402BD0D92D0FB0006B936 /* Debug */, + 825402BE0D92D0FB0006B936 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 825403B50D92D2EC0006B936 /* Build configuration list for PBXNativeTarget "base_gfx" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 825403B20D92D2E50006B936 /* Debug */, + 825403B30D92D2E50006B936 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 825404110D92D35C0006B936 /* Build configuration list for PBXAggregateTarget "All" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 825404030D92D3340006B936 /* Debug */, + 825404040D92D3340006B936 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 825402AA0D92D0C60006B936 /* Project object */; +} diff --git a/base/base_drag_source.cc b/base/base_drag_source.cc new file mode 100644 index 0000000..daf40c7 --- /dev/null +++ b/base/base_drag_source.cc @@ -0,0 +1,88 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <atlbase.h> + +#include "base/base_drag_source.h" + +/////////////////////////////////////////////////////////////////////////////// +// BaseDragSource, public: + +BaseDragSource::BaseDragSource() : ref_count_(0) { +} + +/////////////////////////////////////////////////////////////////////////////// +// BaseDragSource, IDropSource implementation: + +HRESULT BaseDragSource::QueryContinueDrag(BOOL escape_pressed, + DWORD key_state) { + if (escape_pressed) { + OnDragSourceCancel(); + return DRAGDROP_S_CANCEL; + } + + if (!(key_state & MK_LBUTTON)) { + OnDragSourceDrop(); + return DRAGDROP_S_DROP; + } + + OnDragSourceMove(); + return S_OK; +} + +HRESULT BaseDragSource::GiveFeedback(DWORD effect) { + return DRAGDROP_S_USEDEFAULTCURSORS; +} + +/////////////////////////////////////////////////////////////////////////////// +// BaseDragSource, IUnknown implementation: + +HRESULT BaseDragSource::QueryInterface(const IID& iid, void** object) { + *object = NULL; + if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IDropSource)) { + *object = this; + } else { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG BaseDragSource::AddRef() { + return InterlockedIncrement(&ref_count_); +} + +ULONG BaseDragSource::Release() { + if (InterlockedDecrement(&ref_count_) == 0) { + ULONG copied_refcnt = ref_count_; + delete this; + return copied_refcnt; + } + return ref_count_; +} diff --git a/base/base_drag_source.h b/base/base_drag_source.h new file mode 100644 index 0000000..6c5b25d --- /dev/null +++ b/base/base_drag_source.h @@ -0,0 +1,71 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_BASE_DRAG_SOURCE_H__ +#define BASE_BASE_DRAG_SOURCE_H__ + +#include <objidl.h> + +#include "base/basictypes.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// BaseDragSource +// +// A base IDropSource implementation. Handles notifications sent by an active +// drag-drop operation as the user mouses over other drop targets on their +// system. This object tells Windows whether or not the drag should continue, +// and supplies the appropriate cursors. +// +class BaseDragSource : public IDropSource { + public: + BaseDragSource(); + virtual ~BaseDragSource() { } + + // IDropSource implementation: + HRESULT __stdcall QueryContinueDrag(BOOL escape_pressed, DWORD key_state); + HRESULT __stdcall GiveFeedback(DWORD effect); + + // IUnknown implementation: + HRESULT __stdcall QueryInterface(const IID& iid, void** object); + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + + protected: + virtual void OnDragSourceCancel() { } + virtual void OnDragSourceDrop() { } + virtual void OnDragSourceMove() { } + + private: + LONG ref_count_; + + DISALLOW_EVIL_CONSTRUCTORS(BaseDragSource); +}; + +#endif // #ifndef BASE_DRAG_SOURCE_H__ diff --git a/base/base_drop_target.cc b/base/base_drop_target.cc new file mode 100644 index 0000000..f210381 --- /dev/null +++ b/base/base_drop_target.cc @@ -0,0 +1,187 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <shlobj.h> + +#include "base/base_drop_target.h" + +#include "base/logging.h" + +/////////////////////////////////////////////////////////////////////////////// + +IDropTargetHelper* BaseDropTarget::cached_drop_target_helper_ = NULL; + +BaseDropTarget::BaseDropTarget(HWND hwnd) + : suspend_(false), + ref_count_(0), + hwnd_(hwnd) { + DCHECK(hwnd); + HRESULT result = RegisterDragDrop(hwnd, this); +} + +BaseDropTarget::~BaseDropTarget() { +} + +// static +IDropTargetHelper* BaseDropTarget::DropHelper() { + if (!cached_drop_target_helper_) { + CoCreateInstance(CLSID_DragDropHelper, 0, CLSCTX_INPROC_SERVER, + IID_IDropTargetHelper, + reinterpret_cast<void**>(&cached_drop_target_helper_)); + } + return cached_drop_target_helper_; +} + +/////////////////////////////////////////////////////////////////////////////// +// BaseDropTarget, IDropTarget implementation: + +HRESULT BaseDropTarget::DragEnter(IDataObject* data_object, + DWORD key_state, + POINTL cursor_position, + DWORD* effect) { + // Tell the helper that we entered so it can update the drag image. + IDropTargetHelper* drop_helper = DropHelper(); + if (drop_helper) { + drop_helper->DragEnter(GetHWND(), data_object, + reinterpret_cast<POINT*>(&cursor_position), *effect); + } + + // You can't drag and drop within the same HWND. + if (suspend_) { + *effect = DROPEFFECT_NONE; + return S_OK; + } + current_data_object_ = data_object; + POINT screen_pt = { cursor_position.x, cursor_position.y }; + *effect = OnDragEnter(current_data_object_, key_state, screen_pt, *effect); + return S_OK; +} + +HRESULT BaseDropTarget::DragOver(DWORD key_state, + POINTL cursor_position, + DWORD* effect) { + // Tell the helper that we moved over it so it can update the drag image. + IDropTargetHelper* drop_helper = DropHelper(); + if (drop_helper) + drop_helper->DragOver(reinterpret_cast<POINT*>(&cursor_position), *effect); + + if (suspend_) { + *effect = DROPEFFECT_NONE; + return S_OK; + } + + POINT screen_pt = { cursor_position.x, cursor_position.y }; + *effect = OnDragOver(current_data_object_, key_state, screen_pt, *effect); + return S_OK; +} + +HRESULT BaseDropTarget::DragLeave() { + // Tell the helper that we moved out of it so it can update the drag image. + IDropTargetHelper* drop_helper = DropHelper(); + if (drop_helper) + drop_helper->DragLeave(); + + OnDragLeave(current_data_object_); + + current_data_object_ = NULL; + return S_OK; +} + +HRESULT BaseDropTarget::Drop(IDataObject* data_object, + DWORD key_state, + POINTL cursor_position, + DWORD* effect) { + // Tell the helper that we dropped onto it so it can update the drag image. + IDropTargetHelper* drop_helper = DropHelper(); + if (drop_helper) { + drop_helper->Drop(current_data_object_, + reinterpret_cast<POINT*>(&cursor_position), *effect); + } + + if (suspend_) { + *effect = DROPEFFECT_NONE; + return S_OK; + } + + POINT screen_pt = { cursor_position.x, cursor_position.y }; + *effect = OnDrop(current_data_object_, key_state, screen_pt, *effect); + return S_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// BaseDropTarget, IUnknown implementation: + +HRESULT BaseDropTarget::QueryInterface(const IID& iid, void** object) { + *object = NULL; + if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IDropTarget)) { + *object = this; + } else { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG BaseDropTarget::AddRef() { + return InterlockedIncrement(&ref_count_); +} + +ULONG BaseDropTarget::Release() { + if (InterlockedDecrement(&ref_count_) == 0) { + ULONG copied_refcnt = ref_count_; + delete this; + return copied_refcnt; + } + return ref_count_; +} + +DWORD BaseDropTarget::OnDragEnter(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + return DROPEFFECT_NONE; +} + +DWORD BaseDropTarget::OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + return DROPEFFECT_NONE; +} + +void BaseDropTarget::OnDragLeave(IDataObject* data_object) { +} + +DWORD BaseDropTarget::OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + return DROPEFFECT_NONE; +} diff --git a/base/base_drop_target.h b/base/base_drop_target.h new file mode 100644 index 0000000..24e8ee5 --- /dev/null +++ b/base/base_drop_target.h @@ -0,0 +1,142 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_BASE_DROP_TARGET_H__ +#define BASE_BASE_DROP_TARGET_H__ + +#include <atlbase.h> +#include <objidl.h> +#include <shobjidl.h> + +#include "base/basictypes.h" + +// A DropTarget implementation that takes care of the nitty gritty +// of dnd. While this class is concrete, subclasses will most likely +// want to override various OnXXX methods. +// +// Because BaseDropTarget is ref counted you shouldn't delete it directly, +// rather wrap it in a scoped_refptr. Be sure and invoke RevokeDragDrop(m_hWnd) +// before the HWND is deleted too. +class BaseDropTarget : public IDropTarget { + public: + // Create a new BaseDropTarget associating it with the given HWND. + explicit BaseDropTarget(HWND hwnd); + virtual ~BaseDropTarget(); + + // When suspend is set to |true|, the drop target does not receive drops from + // drags initiated within the owning HWND. + // TODO(beng): (http://b/1085385) figure out how we will handle legitimate + // drag-drop operations within the same HWND, such as dragging + // selected text to an edit field. + void set_suspend(bool suspend) { suspend_ = suspend; } + + // IDropTarget implementation: + HRESULT __stdcall DragEnter(IDataObject* data_object, + DWORD key_state, + POINTL cursor_position, + DWORD* effect); + HRESULT __stdcall DragOver(DWORD key_state, + POINTL cursor_position, + DWORD* effect); + HRESULT __stdcall DragLeave(); + HRESULT __stdcall Drop(IDataObject* data_object, + DWORD key_state, + POINTL cursor_position, + DWORD* effect); + + // IUnknown implementation: + HRESULT __stdcall QueryInterface(const IID& iid, void** object); + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + + protected: + // Returns the hosting HWND. + HWND GetHWND() { return hwnd_; } + + // Invoked when the cursor first moves over the hwnd during a dnd session. + // This should return a bitmask of the supported drop operations: + // DROPEFFECT_NONE, DROPEFFECT_COPY, DROPEFFECT_LINK and/or + // DROPEFFECT_MOVE. + virtual DWORD OnDragEnter(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + // Invoked when the cursor moves over the window during a dnd session. + // This should return a bitmask of the supported drop operations: + // DROPEFFECT_NONE, DROPEFFECT_COPY, DROPEFFECT_LINK and/or + // DROPEFFECT_MOVE. + virtual DWORD OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + // Invoked when the cursor moves outside the bounds of the hwnd during a + // dnd session. + virtual void OnDragLeave(IDataObject* data_object); + + // Invoked when the drop ends on the window. This should return the operation + // that was taken. + virtual DWORD OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + private: + // Returns the cached drop helper, creating one if necessary. The returned + // object is not addrefed. May return NULL if the object couldn't be created. + static IDropTargetHelper* DropHelper(); + + // The data object currently being dragged over this drop target. + CComPtr<IDataObject> current_data_object_; + + // A helper object that is used to provide drag image support while the mouse + // is dragging over the content area. + // + // DO NOT ACCESS DIRECTLY! Use DropHelper() instead, which will lazily create + // this if it doesn't exist yet. This object can take tens of milliseconds to + // create, and we don't want to block any window opening for this, especially + // since often, DnD will never be used. Instead, we force this penalty to the + // first time it is actually used. + static IDropTargetHelper* cached_drop_target_helper_; + + // The HWND of the source. This HWND is used to determine coordinates for + // mouse events that are sent to the renderer notifying various drag states. + HWND hwnd_; + + // Whether or not we are currently processing drag notifications for drags + // initiated in this window. + bool suspend_; + + LONG ref_count_; + + DISALLOW_EVIL_CONSTRUCTORS(BaseDropTarget); +}; + +#endif // BASE_BASE_DROP_TARGET_H__ diff --git a/base/base_paths.cc b/base/base_paths.cc new file mode 100644 index 0000000..389b0bb --- /dev/null +++ b/base/base_paths.cc @@ -0,0 +1,151 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/base_paths.h" + +#include <shlobj.h> + +#include "base/file_util.h" +#include "base/path_service.h" + +// This is here for the sole purpose of looking up the corresponding HMODULE. +static int handle_lookup = 0; + +namespace base { + +bool PathProvider(int key, std::wstring* result) { + // NOTE: DIR_CURRENT is a special cased in PathService::Get + + // 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; + + std::wstring cur; + switch (key) { + case base::FILE_EXE: + GetModuleFileName(NULL, system_buffer, MAX_PATH); + cur = 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 + MEMORY_BASIC_INFORMATION info = { 0 }; + VirtualQuery(reinterpret_cast<void*>(&handle_lookup), + &info, sizeof(info)); + // Module handles are just the allocation base address of the module. + HMODULE this_module = reinterpret_cast<HMODULE>(info.AllocationBase); + GetModuleFileName(this_module, system_buffer, MAX_PATH); + cur = system_buffer; + break; + } + case base::DIR_EXE: + PathProvider(base::FILE_EXE, &cur); + file_util::TrimFilename(&cur); + break; + case base::DIR_MODULE: + PathProvider(base::FILE_MODULE, &cur); + file_util::TrimFilename(&cur); + break; + case base::DIR_TEMP: + if (!file_util::GetTempDir(&cur)) + return false; + break; + case base::DIR_WINDOWS: + GetWindowsDirectory(system_buffer, MAX_PATH); + cur = system_buffer; + break; + case base::DIR_SYSTEM: + GetSystemDirectory(system_buffer, MAX_PATH); + cur = system_buffer; + break; + case base::DIR_PROGRAM_FILES: + if (FAILED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = system_buffer; + break; + case base::DIR_SOURCE_ROOT: + // By default, unit tests execute two levels deep from the source root. + // For example: chrome/{Debug|Release}/ui_tests.exe + PathProvider(base::DIR_EXE, &cur); + file_util::UpOneDirectory(&cur); + file_util::UpOneDirectory(&cur); + break; + case base::DIR_APP_DATA: + if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, + system_buffer))) + return false; + cur = system_buffer; + break; + case base::DIR_LOCAL_APP_DATA_LOW: + // TODO(nsylvain): We should use SHGetKnownFolderPath instead. Bug 1281128 + if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, + system_buffer))) + return false; + cur = system_buffer; + file_util::UpOneDirectory(&cur); + file_util::AppendToPath(&cur, L"LocalLow"); + break; + case base::DIR_LOCAL_APP_DATA: + if (FAILED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = 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 = 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 = system_buffer; + break; + case base::DIR_START_MENU: + if (FAILED(SHGetFolderPath(NULL, CSIDL_PROGRAMS, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = system_buffer; + break; + default: + return false; + } + + result->swap(cur); + return true; +} + +} // namespace base diff --git a/base/base_paths.h b/base/base_paths.h new file mode 100644 index 0000000..60a59ca --- /dev/null +++ b/base/base_paths.h @@ -0,0 +1,70 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_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. + +namespace base { + +enum { + PATH_START = 0, + + DIR_CURRENT, // current directory + DIR_EXE, // directory containing FILE_EXE + DIR_MODULE, // directory containing FILE_MODULE + 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 DLL, for example) + DIR_TEMP, // temporary directory + DIR_WINDOWS, // Windows directory, usually "c:\windows" + DIR_SYSTEM, // Usually c:\windows\system32" + DIR_PROGRAM_FILES, // Usually c:\program files + + 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_APP_DATA, // Application Data directory under the user profile. + 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_IE_INTERNET_CACHE, // Temporary Internet Files directory. + DIR_COMMON_START_MENU, // Usually "C:\Documents and Settings\All Users\ + // Start Menu\Programs" + DIR_START_MENU, // Usually "C:\Documents and Settings\<user>\ + // Start Menu\Programs" + PATH_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_H__ diff --git a/base/base_switches.cc b/base/base_switches.cc new file mode 100644 index 0000000..8a587da --- /dev/null +++ b/base/base_switches.cc @@ -0,0 +1,58 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/base_switches.h" + +namespace switches { + +// If the program includes chrome/common/debug_on_start.h, the process will +// 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 wchar_t kDebugOnStart[] = L"debug-on-start"; + +// Will wait for 60 seconds for a debugger to come to attach to the process. +const wchar_t kWaitForDebugger[] = L"wait-for-debugger"; + +// Suppresses all error dialogs when present. +const wchar_t kNoErrorDialogs[] = L"noerrdialogs"; + +// Disables the crash reporting. +const wchar_t kDisableBreakpad[] = L"disable-breakpad"; + +// Generates full memory crash dump. +const wchar_t kFullMemoryCrashReport[] = L"full-memory-crash-report"; + +// The value of this switch determines whether the process is started as a +// renderer or plugin host. If it's empty, it's the browser. +const wchar_t kProcessType[] = L"type"; + +// Enable DCHECKs in release mode. +const wchar_t kEnableDCHECK[] = L"enable-dcheck"; + +} // namespace switches diff --git a/base/base_switches.h b/base/base_switches.h new file mode 100644 index 0000000..bf64a56 --- /dev/null +++ b/base/base_switches.h @@ -0,0 +1,47 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Defines all the "base" command-line switches. + +#ifndef BASE_SWITCHES_H__ +#define BASE_SWITCHES_H__ + +namespace switches { + +extern const wchar_t kDebugOnStart[]; +extern const wchar_t kWaitForDebugger[]; +extern const wchar_t kDisableBreakpad[]; +extern const wchar_t kFullMemoryCrashReport[]; +extern const wchar_t kNoErrorDialogs[]; +extern const wchar_t kProcessType[]; +extern const wchar_t kEnableDCHECK[]; + +} // namespace switches + +#endif // CHROME_COMMON_CHROME_SWITCHES_H__ diff --git a/base/basictypes.h b/base/basictypes.h new file mode 100644 index 0000000..174fb3e --- /dev/null +++ b/base/basictypes.h @@ -0,0 +1,403 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// Copied from base/basictypes.h with some modifications + +#ifndef BASE_BASICTYPES_H__ +#define BASE_BASICTYPES_H__ + +#include <assert.h> // for use with down_cast<> +#include <limits.h> // So we can set the bounds of our types +#include <stddef.h> // For size_t +#include <string.h> // for memcpy + +#include "base/port.h" // Types that only need exist on certain systems + +typedef signed char schar; +typedef signed char int8; +typedef short int16; +// TODO(mbelshe) Remove these type guards. These are +// temporary to avoid conflicts with npapi.h. +#ifndef _INT32 +#define _INT32 +typedef int int32; +#endif +typedef long long int64; + +// NOTE: unsigned types are DANGEROUS in loops and other arithmetical +// places. Use the signed types unless your variable represents a bit +// pattern (eg a hash value) or you really need the extra bit. Do NOT +// use 'unsigned' to express "this value should always be positive"; +// use assertions for this. + +typedef unsigned char uint8; +typedef unsigned short uint16; +// TODO(mbelshe) Remove these type guards. These are +// temporary to avoid conflicts with npapi.h. +#ifndef _UINT32 +#define _UINT32 +typedef unsigned int uint32; +#endif +typedef unsigned long long uint64; + +// 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 = UCHAR_MAX; +const uint16 kuint16max = USHRT_MAX; +const uint32 kuint32max = UINT_MAX; +const uint64 kuint64max = ULLONG_MAX; +const int8 kint8min = SCHAR_MIN; +const int8 kint8max = SCHAR_MAX; +const int16 kint16min = SHRT_MIN; +const int16 kint16max = SHRT_MAX; +const int32 kint32min = INT_MIN; +const int32 kint32max = INT_MAX; +const int64 kint64min = LLONG_MIN; +const int64 kint64max = LLONG_MAX; + +// id for odp categories +typedef uint32 CatId; +const CatId kIllegalCatId = static_cast<CatId>(0); + +typedef uint32 TermId; +const TermId kIllegalTermId = static_cast<TermId>(0); + +typedef uint32 HostId; +const HostId kIllegalHostId = static_cast<HostId>(0); + +typedef uint32 DomainId; +const DomainId kIllegalDomainId = static_cast<DomainId>(0); + +// 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. +#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() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; + +// That gcc wants both of these prototypes seems mysterious. VC, for +// its part, can't decide which to use (another mystery). Matching of +// template overloads: the final frontier. +#ifndef _MSC_VER +template <typename T, size_t N> +char (&ArraySizeHelper(const T (&array)[N]))[N]; +#endif + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +// ARRAYSIZE 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(a) is a compile-time constant of type +// size_t. +// +// ARRAYSIZE catches a few type errors. If you see a compiler error +// +// "warning: division by zero in ..." +// +// when using ARRAYSIZE, you are (wrongfully) giving it a pointer. +// You should only use ARRAYSIZE on statically allocated arrays. +// +// The following comments are on the implementation details, and can +// be ignored by the users. +// +// ARRAYSIZE(arr) works by inspecting sizeof(arr) (the # of bytes in +// the array) and sizeof(*(arr)) (the # of bytes in one array +// element). If the former is divisible by the latter, perhaps arr is +// indeed an array, in which case the division result is the # of +// elements in the array. Otherwise, arr cannot possibly be an array, +// and we generate a compiler error to prevent the code from +// compiling. +// +// Since the size of bool is implementation-defined, we need to cast +// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final +// result has type size_t. +// +// This macro is not perfect as it wrongfully accepts certain +// pointers, namely where the pointer size is divisible by the pointee +// size. Since all our code has to go through a 32-bit compiler, +// where a pointer is 4 bytes, this means all pointers to a type whose +// size is 3 or greater than 4 will be (righteously) rejected. + +#define ARRAYSIZE_UNSAFE(a) \ + ((sizeof(a) / sizeof(*(a))) / \ + static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) + + +// Use implicit_cast as a safe version of static_cast or const_cast +// for upcasting in the type hierarchy (i.e. casting a pointer to Foo +// to a pointer to SuperclassOfFoo or casting a pointer to Foo to +// a const pointer to Foo). +// When you use implicit_cast, the compiler checks that the cast is safe. +// Such explicit implicit_casts are necessary in surprisingly many +// situations where C++ demands an exact type match instead of an +// argument type convertable to a target type. +// +// The From type can be inferred, so the preferred syntax for using +// implicit_cast is the same as for static_cast etc.: +// +// implicit_cast<ToType>(expr) +// +// implicit_cast would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +template<typename To, typename From> +inline To implicit_cast(From const &f) { + return f; +} + + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use implicit_cast<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. + +template<typename To, typename From> // use like this: down_cast<T*>(foo); +inline To down_cast(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) { + implicit_cast<From*, To>(0); + } + + assert(f == NULL || dynamic_cast<To>(f) != NULL); // RTTI: debug mode only! + return static_cast<To>(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(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template <bool> +struct CompileAssert { +}; + +#undef COMPILE_ASSERT +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] + +// Implementation details of COMPILE_ASSERT: +// +// - COMPILE_ASSERT works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outter parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert<bool(expr)> +// +// instead, these compilers will refuse to compile +// +// COMPILE_ASSERT(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + + +// MetatagId refers to metatag-id that we assign to +// each metatag <name, value> pair.. +typedef uint32 MetatagId; + +// Argument type used in interfaces that can optionally take ownership +// of a passed in argument. If TAKE_OWNERSHIP is passed, the called +// object takes ownership of the argument. Otherwise it does not. +enum Ownership { + DO_NOT_TAKE_OWNERSHIP, + TAKE_OWNERSHIP +}; + +// Use these as the mlock_bytes parameter to MLock and MLockGeneral +enum { MLOCK_ALL = -1, MLOCK_NONE = 0 }; + +// Helper routine to avoid buggy code like the following: +// if (pos + N < end) ... +// If pos is large enough, "pos + N" may overflow. For example, +// pos==0xfffff000 and N==1MB. +// +// This often happens on Nacona's in 32-bit mode, because the +// main thread's stack is put very close to address 0xffffffff. +// +// PointerRangeSize(a,b) returns the size of the range [a,b-1] +inline size_t PointerRangeSize(const char* start, const char* end) { + assert(start <= end); + return end - start; +} + +// bit_cast<Dest,Source> is a template function that implements the +// equivalent of "*reinterpret_cast<Dest*>(&source)". We need this in +// very low-level functions like the protobuf library and fast math +// support. +// +// float f = 3.14159265358979; +// int i = bit_cast<int32>(f); +// // i = 0x40490fdb +// +// The classical address-casting method is: +// +// // WRONG +// float f = 3.14159265358979; // WRONG +// int i = * reinterpret_cast<int*>(&f); // WRONG +// +// The address-casting method actually produces undefined behavior +// according to ISO C++ specification section 3.10 -15 -. Roughly, this +// section says: if an object in memory has one type, and a program +// accesses it with a different type, then the result is undefined +// behavior for most values of "different type". +// +// This is true for any cast syntax, either *(int*)&f or +// *reinterpret_cast<int*>(&f). And it is particularly true for +// conversions betweeen integral lvalues and floating-point lvalues. +// +// The purpose of 3.10 -15- is to allow optimizing compilers to assume +// that expressions with different types refer to different memory. gcc +// 4.0.1 has an optimizer that takes advantage of this. So a +// non-conforming program quietly produces wildly incorrect output. +// +// The problem is not the use of reinterpret_cast. The problem is type +// punning: holding an object in memory of one type and reading its bits +// back using a different type. +// +// The C++ standard is more subtle and complex than this, but that +// is the basic idea. +// +// Anyways ... +// +// bit_cast<> calls memcpy() which is blessed by the standard, +// especially by the example in section 3.9 . Also, of course, +// bit_cast<> wraps up the nasty logic in one place. +// +// Fortunately memcpy() is very fast. In optimized mode, with a +// constant size, gcc 2.95.3, gcc 4.0.1, and msvc 7.1 produce inline +// code with the minimal amount of data movement. On a 32-bit system, +// memcpy(d,s,4) compiles to one load and one store, and memcpy(d,s,8) +// compiles to two loads and two stores. +// +// I tested this code with gcc 2.95.3, gcc 4.0.1, icc 8.1, and msvc 7.1. +// +// WARNING: if Dest or Source is a non-POD type, the result of the memcpy +// is likely to surprise you. + +template <class Dest, class Source> +inline Dest bit_cast(const Source& source) { + // Compile time assertion: sizeof(Dest) == sizeof(Source) + // A compile error here means your Dest and Source have different sizes. + typedef char VerifySizesAreEqual [sizeof(Dest) == sizeof(Source) ? 1 : -1]; + + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; +} + + +#endif // BASE_BASICTYPES_H__ diff --git a/base/build/base.vcproj b/base/build/base.vcproj new file mode 100644 index 0000000..0847bbc --- /dev/null +++ b/base/build/base.vcproj @@ -0,0 +1,719 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="base" + ProjectGUID="{1832A374-8A74-4F9E-B536-69A699B3E165}" + RootNamespace="base" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;.\base.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;.\base.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath="..\atomic.h" + > + </File> + <File + RelativePath="..\base_drag_source.cc" + > + </File> + <File + RelativePath="..\base_drag_source.h" + > + </File> + <File + RelativePath="..\base_drop_target.cc" + > + </File> + <File + RelativePath="..\base_drop_target.h" + > + </File> + <File + RelativePath="..\base_paths.cc" + > + </File> + <File + RelativePath="..\base_paths.h" + > + </File> + <File + RelativePath="..\base_switches.cc" + > + </File> + <File + RelativePath="..\base_switches.h" + > + </File> + <File + RelativePath="..\basictypes.h" + > + </File> + <File + RelativePath="..\third_party\nss\blapi.h" + > + </File> + <File + RelativePath="..\third_party\nss\blapit.h" + > + </File> + <File + RelativePath="..\clipboard.cc" + > + </File> + <File + RelativePath="..\clipboard.h" + > + </File> + <File + RelativePath="..\clipboard_util.cc" + > + </File> + <File + RelativePath="..\clipboard_util.h" + > + </File> + <File + RelativePath="..\command_line.cc" + > + </File> + <File + RelativePath="..\command_line.h" + > + </File> + <File + RelativePath="..\condition_variable.cc" + > + </File> + <File + RelativePath="..\condition_variable.h" + > + </File> + <File + RelativePath="..\debug_on_start.cc" + > + </File> + <File + RelativePath="..\debug_on_start.h" + > + </File> + <File + RelativePath="..\debug_util.cc" + > + </File> + <File + RelativePath="..\debug_util.h" + > + </File> + <File + RelativePath="..\event_recorder.cc" + > + </File> + <File + RelativePath="..\event_recorder.h" + > + </File> + <File + RelativePath="..\file_util.cc" + > + </File> + <File + RelativePath="..\file_util.h" + > + </File> + <File + RelativePath="..\file_version_info.cc" + > + </File> + <File + RelativePath="..\file_version_info.h" + > + </File> + <File + RelativePath="..\fix_wp64.h" + > + </File> + <File + RelativePath="..\fixed_string.h" + > + </File> + <File + RelativePath="..\histogram.cc" + > + </File> + <File + RelativePath="..\histogram.h" + > + </File> + <File + RelativePath="..\hmac.cc" + > + </File> + <File + RelativePath="..\hmac.h" + > + </File> + <File + RelativePath="..\iat_patch.cc" + > + </File> + <File + RelativePath="..\iat_patch.h" + > + </File> + <File + RelativePath="..\icu_util.cc" + > + </File> + <File + RelativePath="..\icu_util.h" + > + </File> + <File + RelativePath="..\id_map.h" + > + </File> + <File + RelativePath="..\idle_timer.cc" + > + </File> + <File + RelativePath="..\idle_timer.h" + > + </File> + <File + RelativePath="..\image_util.cc" + > + </File> + <File + RelativePath="..\image_util.h" + > + </File> + <File + RelativePath="..\json_reader.cc" + > + </File> + <File + RelativePath="..\json_reader.h" + > + </File> + <File + RelativePath="..\json_writer.cc" + > + </File> + <File + RelativePath="..\json_writer.h" + > + </File> + <File + RelativePath="..\linked_ptr.h" + > + </File> + <File + RelativePath="..\lock.cc" + > + </File> + <File + RelativePath="..\lock.h" + > + </File> + <File + RelativePath="..\lock_impl.h" + > + </File> + <File + RelativePath="..\lock_impl_win.cc" + > + </File> + <File + RelativePath="..\logging.cc" + > + </File> + <File + RelativePath="..\logging.h" + > + </File> + <File + RelativePath="..\md5.cc" + > + </File> + <File + RelativePath="..\md5.h" + > + </File> + <File + RelativePath="..\memory_debug.cc" + > + </File> + <File + RelativePath="..\memory_debug.h" + > + </File> + <File + RelativePath="..\message_loop.cc" + > + </File> + <File + RelativePath="..\message_loop.h" + > + </File> + <File + RelativePath="..\non_thread_safe.cc" + > + </File> + <File + RelativePath="..\non_thread_safe.h" + > + </File> + <File + RelativePath="..\observer_list.h" + > + </File> + <File + RelativePath="..\path_service.cc" + > + </File> + <File + RelativePath="..\path_service.h" + > + </File> + <File + RelativePath="..\pe_image.cc" + > + </File> + <File + RelativePath="..\pe_image.h" + > + </File> + <File + RelativePath="..\pickle.cc" + > + </File> + <File + RelativePath="..\pickle.h" + > + </File> + <File + RelativePath="..\platform_thread.cc" + > + </File> + <File + RelativePath="..\platform_thread.h" + > + </File> + <File + RelativePath="..\port.h" + > + </File> + <File + RelativePath="..\third_party\nspr\prcpucfg.h" + > + </File> + <File + RelativePath="..\third_party\nspr\prcpucfg_win.h" + > + </File> + <File + RelativePath="..\process.cc" + > + </File> + <File + RelativePath="..\process.h" + > + </File> + <File + RelativePath="..\process_util.cc" + > + </File> + <File + RelativePath="..\process_util.h" + > + </File> + <File + RelativePath="..\third_party\nspr\prtime.cc" + > + </File> + <File + RelativePath="..\third_party\nspr\prtime.h" + > + </File> + <File + RelativePath="..\third_party\nspr\prtypes.h" + > + </File> + <File + RelativePath="..\third_party\purify\pure.h" + > + </File> + <File + RelativePath="..\third_party\purify\pure_api.c" + > + </File> + <File + RelativePath="..\ref_counted.h" + > + </File> + <File + RelativePath="..\registry.cc" + > + </File> + <File + RelativePath="..\registry.h" + > + </File> + <File + RelativePath="..\resource_util.cc" + > + </File> + <File + RelativePath="..\resource_util.h" + > + </File> + <File + RelativePath="..\revocable_store.cc" + > + </File> + <File + RelativePath="..\revocable_store.h" + > + </File> + <File + RelativePath="..\scoped_handle.h" + > + </File> + <File + RelativePath="..\scoped_ptr.h" + > + </File> + <File + RelativePath="..\sha2.cc" + > + </File> + <File + RelativePath="..\sha2.h" + > + </File> + <File + RelativePath="..\third_party\nss\sha256.h" + > + </File> + <File + RelativePath="..\third_party\nss\sha512.cc" + > + </File> + <File + RelativePath="..\shared_event.cc" + > + </File> + <File + RelativePath="..\shared_event.h" + > + </File> + <File + RelativePath="..\shared_memory.cc" + > + </File> + <File + RelativePath="..\shared_memory.h" + > + </File> + <File + RelativePath="..\singleton.h" + > + </File> + <File + RelativePath="..\singleton_internal.h" + > + </File> + <File + RelativePath="..\spin_wait.h" + > + </File> + <File + RelativePath="..\stack_container.h" + > + </File> + <File + RelativePath="..\stats_counters.h" + > + </File> + <File + RelativePath="..\stats_table.cc" + > + </File> + <File + RelativePath="..\stats_table.h" + > + </File> + <File + RelativePath="..\string16.h" + > + </File> + <File + RelativePath="..\string_escape.cc" + > + </File> + <File + RelativePath="..\string_escape.h" + > + </File> + <File + RelativePath="..\string_piece.cc" + > + </File> + <File + RelativePath="..\string_piece.h" + > + </File> + <File + RelativePath="..\string_tokenizer.h" + > + </File> + <File + RelativePath="..\string_util.cc" + > + </File> + <File + RelativePath="..\string_util.h" + > + </File> + <File + RelativePath="..\string_util_icu.cc" + > + </File> + <File + RelativePath="..\string_util_win.cc" + > + </File> + <File + RelativePath="..\string_util_win.h" + > + </File> + <File + RelativePath="..\task.h" + > + </File> + <File + RelativePath="..\thread.cc" + > + </File> + <File + RelativePath="..\thread.h" + > + </File> + <File + RelativePath="..\thread_local_storage.h" + > + </File> + <File + RelativePath="..\thread_local_storage_win.cc" + > + </File> + <File + RelativePath="..\time.cc" + > + </File> + <File + RelativePath="..\time.h" + > + </File> + <File + RelativePath="..\time_win.cc" + > + </File> + <File + RelativePath="..\timer.cc" + > + </File> + <File + RelativePath="..\timer.h" + > + </File> + <File + RelativePath="..\tracked.cc" + > + </File> + <File + RelativePath="..\tracked.h" + > + </File> + <File + RelativePath="..\tracked_objects.cc" + > + </File> + <File + RelativePath="..\tracked_objects.h" + > + </File> + <File + RelativePath="..\tuple.h" + > + </File> + <File + RelativePath="..\values.cc" + > + </File> + <File + RelativePath="..\values.h" + > + </File> + <File + RelativePath="..\watchdog.cc" + > + </File> + <File + RelativePath="..\watchdog.h" + > + </File> + <File + RelativePath="..\win_util.cc" + > + </File> + <File + RelativePath="..\win_util.h" + > + </File> + <File + RelativePath="..\windows_message_list.h" + > + </File> + <File + RelativePath="..\wmi_util.cc" + > + </File> + <File + RelativePath="..\wmi_util.h" + > + </File> + <File + RelativePath="..\word_iterator.cc" + > + </File> + <File + RelativePath="..\word_iterator.h" + > + </File> + <File + RelativePath="..\worker_pool.cc" + > + </File> + <File + RelativePath="..\worker_pool.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/base/build/base.vsprops b/base/build/base.vsprops new file mode 100644 index 0000000..63e2723 --- /dev/null +++ b/base/build/base.vsprops @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="base" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > +</VisualStudioPropertySheet> diff --git a/base/build/base_gfx.vcproj b/base/build/base_gfx.vcproj new file mode 100644 index 0000000..ac60c50 --- /dev/null +++ b/base/build/base_gfx.vcproj @@ -0,0 +1,263 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="base_gfx" + ProjectGUID="{A508ADD3-CECE-4E0F-8448-2F5E454DF551}" + RootNamespace="base_gfx" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;.\base_gfx.vsprops;..\..\skia\using_skia.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;.\base_gfx.vsprops;..\..\skia\using_skia.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath="..\gfx\bitmap_header.cc" + > + </File> + <File + RelativePath="..\gfx\bitmap_header.h" + > + </File> + <File + RelativePath="..\gfx\bitmap_platform_device.cc" + > + </File> + <File + RelativePath="..\gfx\bitmap_platform_device.h" + > + </File> + <File + RelativePath="..\gfx\convolver.cc" + > + </File> + <File + RelativePath="..\gfx\convolver.h" + > + </File> + <File + RelativePath="..\gfx\font_utils.cc" + > + </File> + <File + RelativePath="..\gfx\font_utils.h" + > + </File> + <File + RelativePath="..\gfx\image_operations.cc" + > + </File> + <File + RelativePath="..\gfx\image_operations.h" + > + </File> + <File + RelativePath="..\gfx\native_theme.cc" + > + </File> + <File + RelativePath="..\gfx\native_theme.h" + > + </File> + <File + RelativePath="..\gfx\platform_canvas.cc" + > + </File> + <File + RelativePath="..\gfx\platform_canvas.h" + > + </File> + <File + RelativePath="..\gfx\platform_device.cc" + > + </File> + <File + RelativePath="..\gfx\platform_device.h" + > + </File> + <File + RelativePath="..\gfx\png_decoder.cc" + > + </File> + <File + RelativePath="..\gfx\png_decoder.h" + > + </File> + <File + RelativePath="..\gfx\png_encoder.cc" + > + </File> + <File + RelativePath="..\gfx\png_encoder.h" + > + </File> + <File + RelativePath="..\gfx\point.cc" + > + </File> + <File + RelativePath="..\gfx\point.h" + > + </File> + <File + RelativePath="..\gfx\rect.cc" + > + </File> + <File + RelativePath="..\gfx\rect.h" + > + </File> + <File + RelativePath="..\gfx\size.cc" + > + </File> + <File + RelativePath="..\gfx\size.h" + > + </File> + <File + RelativePath="..\gfx\skia_utils.cc" + > + </File> + <File + RelativePath="..\gfx\skia_utils.h" + > + </File> + <File + RelativePath="..\gfx\uniscribe.cc" + > + </File> + <File + RelativePath="..\gfx\uniscribe.h" + > + </File> + <File + RelativePath="..\gfx\vector_canvas.cc" + > + </File> + <File + RelativePath="..\gfx\vector_canvas.h" + > + </File> + <File + RelativePath="..\gfx\vector_device.cc" + > + </File> + <File + RelativePath="..\gfx\vector_device.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/base/build/base_gfx.vsprops b/base/build/base_gfx.vsprops new file mode 100644 index 0000000..9dc5341 --- /dev/null +++ b/base/build/base_gfx.vsprops @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="base_gfx" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;..\..\skia\using_skia.vsprops" + > +</VisualStudioPropertySheet> diff --git a/base/build/base_unittests.vcproj b/base/build/base_unittests.vcproj new file mode 100644 index 0000000..f9e6787 --- /dev/null +++ b/base/build/base_unittests.vcproj @@ -0,0 +1,370 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="base_unittests" + ProjectGUID="{27A30967-4BBA-48D1-8522-CDE95F7B1CEC}" + RootNamespace="base_unittests" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;.\base_unittests.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="UNIT_TEST" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;.\base_unittests.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="UNIT_TEST" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="support" + > + <File + RelativePath="..\check_handler.h" + > + </File> + <File + RelativePath="..\multiprocess_test.h" + > + </File> + <File + RelativePath="..\no_windows2000_unittest.h" + > + </File> + <File + RelativePath="..\run_all_unittests.cc" + > + </File> + <File + RelativePath="..\test_suite.h" + > + </File> + </Filter> + <Filter + Name="base_tests" + > + <File + RelativePath="..\atomic_unittest.cc" + > + </File> + <File + RelativePath="..\check_handler_unittest.cc" + > + </File> + <File + RelativePath="..\clipboard_unittest.cc" + > + </File> + <File + RelativePath="..\command_line_unittest.cc" + > + </File> + <File + RelativePath="..\condition_variable_test.cc" + > + </File> + <File + RelativePath="..\file_util_unittest.cc" + > + </File> + <File + RelativePath="..\file_version_info_unittest.cc" + > + </File> + <File + RelativePath="..\fixed_string_unittest.cc" + > + </File> + <File + RelativePath="..\histogram_test.cc" + > + </File> + <File + RelativePath="..\hmac_unittest.cc" + > + </File> + <File + RelativePath="..\idletimer_unittest.cc" + > + </File> + <File + RelativePath="..\json_reader_unittest.cc" + > + </File> + <File + RelativePath="..\json_writer_unittest.cc" + > + </File> + <File + RelativePath="..\linked_ptr_unittest.cc" + > + </File> + <File + RelativePath="..\message_loop_unittest.cc" + > + </File> + <File + RelativePath="..\gfx\native_theme_unittest.cc" + > + </File> + <File + RelativePath="..\path_service_unittest.cc" + > + </File> + <File + RelativePath="..\pe_image_unittest.cc" + > + </File> + <File + RelativePath="..\pickle_unittest.cc" + > + </File> + <File + RelativePath="..\pr_time_test.cc" + > + </File> + <File + RelativePath="..\process_util_unittest.cc" + > + </File> + <File + RelativePath="..\gfx\rect_unittest.cc" + > + </File> + <File + RelativePath="..\ref_counted_unittest.cc" + > + </File> + <File + RelativePath="..\sha2_unittest.cc" + > + </File> + <File + RelativePath="..\shared_event_unittest.cc" + > + </File> + <File + RelativePath="..\shared_memory_unittest.cc" + > + </File> + <File + RelativePath="..\singleton_unittest.cc" + > + </File> + <File + RelativePath="..\stack_container_unittest.cc" + > + </File> + <File + RelativePath="..\stats_table_unittest.cc" + > + </File> + <File + RelativePath="..\string_escape_unittest.cc" + > + </File> + <File + RelativePath="..\string_piece_unittest.cc" + > + </File> + <File + RelativePath="..\string_tokenizer_unittest.cc" + > + </File> + <File + RelativePath="..\string_util_unittest.cc" + > + </File> + <File + RelativePath="..\thread_local_storage_unittest.cc" + > + </File> + <File + RelativePath="..\thread_unittest.cc" + > + </File> + <File + RelativePath="..\time_unittest.cc" + > + </File> + <File + RelativePath="..\timer_unittest.cc" + > + </File> + <File + RelativePath="..\tracked_objects_test.cc" + > + </File> + <File + RelativePath="..\values_unittest.cc" + > + </File> + <File + RelativePath="..\watchdog_test.cc" + > + </File> + <File + RelativePath="..\win_util_unittest.cc" + > + </File> + <File + RelativePath="..\wmi_util_unittest.cc" + > + </File> + </Filter> + <Filter + Name="gfx_tests" + > + <File + RelativePath="..\gfx\convolver_unittest.cc" + > + </File> + <File + RelativePath="..\gfx\image_operations_unittest.cc" + > + </File> + <File + RelativePath="..\gfx\platform_canvas_unittest.cc" + > + </File> + <File + RelativePath="..\gfx\png_codec_unittest.cc" + > + </File> + <File + RelativePath="..\gfx\uniscribe_unittest.cc" + > + </File> + <File + RelativePath="..\gfx\vector_canvas_unittest.cc" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/base/build/base_unittests.vsprops b/base/build/base_unittests.vsprops new file mode 100644 index 0000000..047887d --- /dev/null +++ b/base/build/base_unittests.vsprops @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="base_unittests" + InheritedPropertySheets=".\base_gfx.vsprops" + > +</VisualStudioPropertySheet> diff --git a/base/build/debug_message.vcproj b/base/build/debug_message.vcproj new file mode 100644 index 0000000..8dac295 --- /dev/null +++ b/base/build/debug_message.vcproj @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="debug_message" + ProjectGUID="{0E5474AC-5996-4B13-87C0-4AE931EE0815}" + RootNamespace="DebugMessage" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + SubSystem="2" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + SubSystem="2" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath="..\debug_message.cc" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/base/build/singleton_dll_unittest.def b/base/build/singleton_dll_unittest.def new file mode 100644 index 0000000..6ad4d90 --- /dev/null +++ b/base/build/singleton_dll_unittest.def @@ -0,0 +1,9 @@ +EXPORTS + SingletonInt1 + SingletonInt2 + SingletonInt3 + SingletonInt4 + SingletonInt5 + SingletonNoLeak + SingletonLeak + GetLeakySingleton diff --git a/base/build/singleton_dll_unittest.vsprops b/base/build/singleton_dll_unittest.vsprops new file mode 100644 index 0000000..a3eb79b --- /dev/null +++ b/base/build/singleton_dll_unittest.vsprops @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="singleton_dll_unittest" + > + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="SINGLETON_UNITTEST_EXPORTS" + /> + <Tool + Name="VCLinkerTool" + ModuleDefinitionFile="singleton_dll_unittest.def" + /> +</VisualStudioPropertySheet> diff --git a/base/build/singleton_unittest.vcproj b/base/build/singleton_unittest.vcproj new file mode 100644 index 0000000..6fcb715 --- /dev/null +++ b/base/build/singleton_unittest.vcproj @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="singleton_dll_unittest" + ProjectGUID="{E457F2FB-4708-4001-9B1C-275D7BD7F2A8}" + RootNamespace="singleton_unittest" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="2" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;.\singleton_dll_unittest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="2" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;.\singleton_dll_unittest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath="..\singleton_dll_unittest.cc" + > + </File> + <File + RelativePath=".\singleton_dll_unittest.def" + > + </File> + <File + RelativePath="..\singleton_dll_unittest.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/base/check_handler.h b/base/check_handler.h new file mode 100644 index 0000000..cf6dd67 --- /dev/null +++ b/base/check_handler.h @@ -0,0 +1,112 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_CHECK_HANDLER_H__ +#define BASE_CHECK_HANDLER_H__ + +#include <windows.h> + +#include "base/logging.h" + +// This class allows temporary handling of assert firing. When a CHECK() +// or DCHECK() assertion happens it will in turn generate a SEH exception +// which can be can captured using a windows SEH hander __try .. _except +// block. One practical use of this class is for unit tests that make sure +// CHECK conditions are appropriately handled. For example: +// +// TEST(TestGroup, VerifyAssert) { +// CheckAssertHandler expect_exception; +// __try { +// MyClass object; // MyClass dtor will not be called. +// Param some_bad_param; +// object.Method(some_bad_param); // Should triggers a CHECK(). +// ADD_FAILURE(); // If we get here the test failed. +// } __except(EXCEPTION_EXECUTE_HANDLER) { +// DWORD ecode = GetExceptionCode(); +// EXPECT_EQ(CheckAssertHandler::seh_exception_code(), ecode); +// } +// } +// +// You can put MyClass outside the __try block so its destructor will be +// called which could lead to a crash if the state of the object is +// corrupted by the CHECK you are testing. If that is the case you should +// fix the state of the object inside the __except block. +// +// Since the above code is Windows specific, two helper macros are provided +// that hide the implementation details. Using the macros the code becomes: +// +// TEST(TestGroup, VerifyAssert) { +// CHECK_HANDLER_BEGIN +// MyClass object; // MyClass dtor will not be called. +// Param some_bad_param; +// object.Method(some_bad_param); // Should triggers a CHECK(). +// CHECK_HANDLER_END +// } +// +// Depending on the compiler settings you might have issue this pragma arround +// the code that uses this class: +// #pragma warning(disable: 4509) +// Which tells the compiler that is ok that some dtors will not be called. +// +// Create this object on the stack always.Do not create it inside the +// __try block itself or the dtor will never be called. Create only one +// on each scope. +// +// The key detail here is the RaiseException() call which transfers +// program control away from the code that caused the assertion and back +// into the _except block. + +class CheckAssertHandler { + public: + // Installs the assert handler. The dtor will remove the handler. + CheckAssertHandler() { + logging::SetLogAssertHandler(&CheckAssertHandler::LogAssertHandler); + } + ~CheckAssertHandler() { + logging::SetLogAssertHandler(NULL); + } + static DWORD seh_exception_code() { return 0x1765413; } + private: + static void LogAssertHandler(const std::string&) { + ::RaiseException(seh_exception_code(), 0, 0, NULL); + } +}; + +#define CHECK_HANDLER_BEGIN \ + CheckAssertHandler chk_ex_handler; \ + __try { + +#define CHECK_HANDLER_END \ + ADD_FAILURE(); \ + } __except(EXCEPTION_EXECUTE_HANDLER) { \ + DWORD ecode = GetExceptionCode(); \ + EXPECT_EQ(CheckAssertHandler::seh_exception_code(), ecode); \ + } + +#endif // BASE_CHECK_HANDLER_H__ diff --git a/base/check_handler_unittest.cc b/base/check_handler_unittest.cc new file mode 100644 index 0000000..ba03038 --- /dev/null +++ b/base/check_handler_unittest.cc @@ -0,0 +1,72 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/check_handler.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SimpleTestClass { + public: + SimpleTestClass() { + } + ~SimpleTestClass() { + ADD_FAILURE(); + } + void ThisMethodAsserts() { + CHECK(false); + ADD_FAILURE(); + } +}; + +void ThisFunctionAsserts() { + CHECK(false); + ADD_FAILURE(); +} + +} // namespace + +#pragma warning(push) +#pragma warning(disable: 4509) + +TEST(CheckHandlerTest, TestMacroCheckObj) { + CHECK_HANDLER_BEGIN + SimpleTestClass object; + object.ThisMethodAsserts(); + CHECK_HANDLER_END +} + +TEST(CheckHandlerTest, TestMacroCheckFunc) { + CHECK_HANDLER_BEGIN + ThisFunctionAsserts(); + CHECK_HANDLER_END +} + +#pragma warning(pop) diff --git a/base/clipboard.cc b/base/clipboard.cc new file mode 100644 index 0000000..b85ec00 --- /dev/null +++ b/base/clipboard.cc @@ -0,0 +1,658 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Many of these functions are based on those found in +// webkit/port/platform/PasteboardWin.cpp + +#include <shlobj.h> +#include <shellapi.h> + +#include "base/clipboard.h" + +#include "base/clipboard_util.h" +#include "base/logging.h" +#include "base/string_util.h" + +namespace { + +// A small object to ensure we close the clipboard after opening it. +class ClipboardLock { + public: + ClipboardLock() : we_own_the_lock_(false) { } + + ~ClipboardLock() { + if (we_own_the_lock_) + Release(); + } + + bool Acquire(HWND owner) { + // We shouldn't be calling this if we already own the clipbard lock. + DCHECK(!we_own_the_lock_); + + // We already have the lock. We don't want to stomp on the other use. + if (we_own_the_lock_) + return false; + + const int kMaxAttemptsToOpenClipboard = 5; + + // Attempt to acquire the clipboard lock. This may fail if another process + // currently holds the lock. We're willing to try a few times in the hopes + // of acquiring it. + // + // This turns out to be an issue when using remote desktop because the + // rdpclip.exe process likes to read what we've written to the clipboard and + // send it to the RDP client. If we open and close the clipboard in quick + // succession, we might be trying to open it while rdpclip.exe has it open, + // See Bug 815425. + // + // In fact, we believe we'll only spin this loop over remote desktop. In + // normal situations, the user is initiating clipboard operations and there + // shouldn't be lock contention. + + for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) { + if (::OpenClipboard(owner)) { + we_own_the_lock_ = true; + return we_own_the_lock_; + } + + // Having failed, we yeild our timeslice to other processes. ::Yield seems + // to be insufficient here, so we sleep for 5 ms. + if (attempts < (kMaxAttemptsToOpenClipboard - 1)) + ::Sleep(5); + } + + // We failed to acquire the clipboard. + return false; + } + + void Release() { + // We should only be calling this if we already own the clipbard lock. + DCHECK(we_own_the_lock_); + + // We we don't have the lock, there is nothing to release. + if (!we_own_the_lock_) + return; + + ::CloseClipboard(); + we_own_the_lock_ = false; + } + + private: + bool we_own_the_lock_; +}; + +LRESULT CALLBACK ClipboardOwnerWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + LRESULT lresult = 0; + + switch(message) { + case WM_RENDERFORMAT: + // This message comes when SetClipboardData was sent a null data handle + // and now it's come time to put the data on the clipboard. + // We always set data, so there isn't a need to actually do anything here. + break; + case WM_RENDERALLFORMATS: + // This message comes when SetClipboardData was sent a null data handle + // and now this application is about to quit, so it must put data on + // the clipboard before it exits. + // We always set data, so there isn't a need to actually do anything here. + break; + case WM_DRAWCLIPBOARD: + break; + case WM_DESTROY: + break; + case WM_CHANGECBCHAIN: + break; + default: + lresult = DefWindowProc(hwnd, message, wparam, lparam); + break; + } + return lresult; +} + +template <typename charT> +HGLOBAL CreateGlobalData(const std::basic_string<charT>& str) { + HGLOBAL data = + ::GlobalAlloc(GMEM_MOVEABLE, ((str.size() + 1) * sizeof(charT))); + if (data) { + charT* raw_data = static_cast<charT*>(::GlobalLock(data)); + memcpy(raw_data, str.data(), str.size() * sizeof(charT)); + raw_data[str.size()] = '\0'; + ::GlobalUnlock(data); + } + return data; +}; + +} // namespace + +Clipboard::Clipboard() { + // make a dummy HWND to be the clipboard's owner + WNDCLASSEX wcex = {0}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = ClipboardOwnerWndProc; + wcex.hInstance = GetModuleHandle(NULL); + wcex.lpszClassName = L"ClipboardOwnerWindowClass"; + ::RegisterClassEx(&wcex); + + clipboard_owner_ = ::CreateWindow(L"ClipboardOwnerWindowClass", + L"ClipboardOwnerWindow", + 0, 0, 0, 0, 0, + HWND_MESSAGE, + 0, 0, 0); +} + +Clipboard::~Clipboard() { + ::DestroyWindow(clipboard_owner_); + clipboard_owner_ = NULL; +} + +void Clipboard::Clear() const { + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + ::EmptyClipboard(); +} + +void Clipboard::WriteText(const std::wstring& text) const { + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + HGLOBAL glob = CreateGlobalData(text); + if (glob && !::SetClipboardData(CF_UNICODETEXT, glob)) + ::GlobalFree(glob); +} + +void Clipboard::WriteHTML(const std::wstring& markup, + const std::string& url) const { + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + std::string html_fragment; + MarkupToHTMLClipboardFormat(markup, url, &html_fragment); + HGLOBAL glob = CreateGlobalData(html_fragment); + if (glob && !::SetClipboardData(ClipboardUtil::GetHtmlFormat()->cfFormat, + glob)) { + ::GlobalFree(glob); + } +} + +void Clipboard::WriteBookmark(const std::wstring& title, + const std::string& url) const { + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + std::wstring bookmark(title); + bookmark.append(1, L'\n'); + bookmark.append(UTF8ToWide(url)); + HGLOBAL glob = CreateGlobalData(bookmark); + if (glob && !::SetClipboardData(ClipboardUtil::GetUrlWFormat()->cfFormat, + glob)) { + ::GlobalFree(glob); + } +} + +void Clipboard::WriteHyperlink(const std::wstring& title, + const std::string& url) const { + // Write as a bookmark. + WriteBookmark(title, url); + + // Build the HTML link. + std::wstring link(L"<a href=\""); + link.append(UTF8ToWide(url)); + link.append(L"\">"); + link.append(title); + link.append(L"</a>"); + + // Write as an HTML link. + WriteHTML(link, std::string()); +} + +void Clipboard::WriteWebSmartPaste() const { + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + SetClipboardData(ClipboardUtil::GetWebKitSmartPasteFormat()->cfFormat, NULL); +} + +void Clipboard::WriteBitmap(const void* pixels, const gfx::Size& size) const { + HDC dc = ::GetDC(NULL); + + // This doesn't actually cost us a memcpy when the bitmap comes from the + // renderer as we load it into the bitmap using setPixels which just sets a + // pointer. Someone has to memcpy it into GDI, it might as well be us here. + + // TODO(darin): share data in gfx/bitmap_header.cc somehow + BITMAPINFO bm_info = {0}; + bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bm_info.bmiHeader.biWidth = size.width(); + bm_info.bmiHeader.biHeight = -size.height(); // sets vertical orientation + bm_info.bmiHeader.biPlanes = 1; + bm_info.bmiHeader.biBitCount = 32; + bm_info.bmiHeader.biCompression = BI_RGB; + + // ::CreateDIBSection allocates memory for us to copy our bitmap into. + // Unfortunately, we can't write the created bitmap to the clipboard, + // (see http://msdn2.microsoft.com/en-us/library/ms532292.aspx) + void *bits; + HBITMAP source_hbitmap = + ::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, &bits, NULL, 0); + + if (bits && source_hbitmap) { + // Copy the bitmap out of shared memory and into GDI + memcpy(bits, pixels, 4 * size.width() * size.height()); + + // Now we have an HBITMAP, we can write it to the clipboard + WriteBitmapFromHandle(source_hbitmap, size); + } + + ::DeleteObject(source_hbitmap); + ::ReleaseDC(NULL, dc); +} + +void Clipboard::WriteBitmapFromSharedMemory(const SharedMemory& bitmap, + const gfx::Size& size) const { + // TODO(darin): share data in gfx/bitmap_header.cc somehow + BITMAPINFO bm_info = {0}; + bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bm_info.bmiHeader.biWidth = size.width(); + bm_info.bmiHeader.biHeight = -size.height(); // Sets the vertical orientation + bm_info.bmiHeader.biPlanes = 1; + bm_info.bmiHeader.biBitCount = 32; + bm_info.bmiHeader.biCompression = BI_RGB; + + HDC dc = ::GetDC(NULL); + + // We can create an HBITMAP directly using the shared memory handle, saving + // a memcpy. + HBITMAP source_hbitmap = + ::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, NULL, bitmap.handle(), 0); + + if (source_hbitmap) { + // Now we can write the HBITMAP to the clipboard + WriteBitmapFromHandle(source_hbitmap, size); + } + + ::DeleteObject(source_hbitmap); + ::ReleaseDC(NULL, dc); +} + +void Clipboard::WriteBitmapFromHandle(HBITMAP source_hbitmap, + const gfx::Size& size) const { + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + // We would like to just call ::SetClipboardData on the source_hbitmap, + // but that bitmap might not be of a sort we can write to the clipboard. + // For this reason, we create a new bitmap, copy the bits over, and then + // write that to the clipboard. + + HDC dc = ::GetDC(NULL); + HDC compatible_dc = ::CreateCompatibleDC(NULL); + HDC source_dc = ::CreateCompatibleDC(NULL); + + // This is the HBITMAP we will eventually write to the clipboard + HBITMAP hbitmap = ::CreateCompatibleBitmap(dc, size.width(), size.height()); + if (!hbitmap) { + // Failed to create the bitmap + ::DeleteDC(compatible_dc); + ::DeleteDC(source_dc); + ::ReleaseDC(NULL, dc); + return; + } + + HBITMAP old_hbitmap = (HBITMAP)SelectObject(compatible_dc, hbitmap); + HBITMAP old_source = (HBITMAP)SelectObject(source_dc, source_hbitmap); + + // Now we need to blend it into an HBITMAP we can place on the clipboard + BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + ::AlphaBlend(compatible_dc, 0, 0, size.width(), size.height(), + source_dc, 0, 0, size.width(), size.height(), bf); + + // Clean up all the handles we just opened + ::SelectObject(compatible_dc, old_hbitmap); + ::SelectObject(source_dc, old_source); + ::DeleteObject(old_hbitmap); + ::DeleteObject(old_source); + ::DeleteDC(compatible_dc); + ::DeleteDC(source_dc); + ::ReleaseDC(NULL, dc); + + // Actually write the bitmap to the clipboard + ::SetClipboardData(CF_BITMAP, hbitmap); +} + +// Write a file or set of files to the clipboard in HDROP format. When the user +// invokes a paste command (in a Windows explorer shell, for example), the files +// will be copied to the paste location. +void Clipboard::WriteFile(const std::wstring& file) const { + std::vector<std::wstring> files; + files.push_back(file); + WriteFiles(files); +} + +void Clipboard::WriteFiles(const std::vector<std::wstring>& files) const { + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + // Calculate the amount of space we'll need store the strings: require + // NULL terminator between strings, and double null terminator at the end. + size_t bytes = sizeof(DROPFILES); + for (size_t i = 0; i < files.size(); ++i) + bytes += (files[i].length() + 1) * sizeof(wchar_t); + bytes += sizeof(wchar_t); + + HANDLE hdata = ::GlobalAlloc(GMEM_MOVEABLE, bytes); + if (!hdata) + return; + + DROPFILES* drop_files = static_cast<DROPFILES*>(::GlobalLock(hdata)); + drop_files->pFiles = sizeof(DROPFILES); + drop_files->fWide = TRUE; + BYTE* data = reinterpret_cast<BYTE*>(drop_files) + sizeof(DROPFILES); + + // Copy the strings stored in 'files' with proper NULL separation. + wchar_t* data_pos = reinterpret_cast<wchar_t*>(data); + for (size_t i = 0; i < files.size(); ++i) { + size_t offset = files[i].length() + 1; + memcpy(data_pos, files[i].c_str(), offset * sizeof(wchar_t)); + data_pos += offset; + } + data_pos[0] = L'\0'; // Double NULL termination after the last string. + + ::GlobalUnlock(hdata); + if (!::SetClipboardData(CF_HDROP, hdata)) + ::GlobalFree(hdata); +} + +bool Clipboard::IsFormatAvailable(unsigned int format) const { + return ::IsClipboardFormatAvailable(format) != FALSE; +} + +void Clipboard::ReadText(std::wstring* result) const { + if (!result) { + NOTREACHED(); + return; + } + + result->clear(); + + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + HANDLE data = ::GetClipboardData(CF_UNICODETEXT); + if (!data) + return; + + result->assign(static_cast<const wchar_t*>(::GlobalLock(data))); + ::GlobalUnlock(data); +} + +void Clipboard::ReadAsciiText(std::string* result) const { + if (!result) { + NOTREACHED(); + return; + } + + result->clear(); + + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + HANDLE data = ::GetClipboardData(CF_TEXT); + if (!data) + return; + + result->assign(static_cast<const char*>(::GlobalLock(data))); + ::GlobalUnlock(data); +} + +void Clipboard::ReadHTML(std::wstring* markup, std::string* src_url) const { + if (markup) + markup->clear(); + + if (src_url) + src_url->clear(); + + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + HANDLE data = ::GetClipboardData(ClipboardUtil::GetHtmlFormat()->cfFormat); + if (!data) + return; + + std::string html_fragment(static_cast<const char*>(::GlobalLock(data))); + ::GlobalUnlock(data); + + ParseHTMLClipboardFormat(html_fragment, markup, src_url); +} + +void Clipboard::ReadBookmark(std::wstring* title, std::string* url) const { + if (title) + title->clear(); + + if (url) + url->clear(); + + // Acquire the clipboard. + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + HANDLE data = ::GetClipboardData(ClipboardUtil::GetUrlWFormat()->cfFormat); + if (!data) + return; + + std::wstring bookmark(static_cast<const wchar_t*>(::GlobalLock(data))); + ::GlobalUnlock(data); + + ParseBookmarkClipboardFormat(bookmark, title, url); +} + +// Read a file in HDROP format from the clipboard. +void Clipboard::ReadFile(std::wstring* file) const { + if (!file) { + NOTREACHED(); + return; + } + + file->clear(); + std::vector<std::wstring> files; + ReadFiles(&files); + + // Take the first file, if available. + if (!files.empty()) + file->assign(files[0]); +} + +// Read a set of files in HDROP format from the clipboard. +void Clipboard::ReadFiles(std::vector<std::wstring>* files) const { + if (!files) { + NOTREACHED(); + return; + } + + files->clear(); + + ClipboardLock lock; + if (!lock.Acquire(clipboard_owner_)) + return; + + HDROP drop = static_cast<HDROP>(::GetClipboardData(CF_HDROP)); + if (!drop) + return; + + // Count of files in the HDROP. + int count = ::DragQueryFile(drop, 0xffffffff, NULL, 0); + + if (count) { + for (int i = 0; i < count; ++i) { + int size = ::DragQueryFile(drop, i, NULL, 0) + 1; + std::wstring file; + ::DragQueryFile(drop, i, WriteInto(&file, size), size); + files->push_back(file); + } + } +} + +// static +void Clipboard::MarkupToHTMLClipboardFormat(const std::wstring& markup, + const std::string& src_url, + std::string* html_fragment) { + DCHECK(html_fragment); + // Documentation for the CF_HTML format is available at + // http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp + + if (markup.empty()) { + html_fragment->clear(); + return; + } + + std::string markup_utf8 = WideToUTF8(markup); + + html_fragment->assign("Version:0.9"); + + std::string start_html("\nStartHTML:"); + std::string end_html("\nEndHTML:"); + std::string start_fragment("\nStartFragment:"); + std::string end_fragment("\nEndFragment:"); + std::string source_url("\nSourceURL:"); + + bool has_source_url = !src_url.empty() && + !StartsWithASCII(src_url, "about:", false); + if (has_source_url) + source_url.append(src_url); + + std::string start_markup("\n<HTML>\n<BODY>\n<!--StartFragment-->\n"); + std::string end_markup("\n<!--EndFragment-->\n</BODY>\n</HTML>"); + + // calculate offsets + const size_t kMaxDigits = 10; // number of digits in UINT_MAX in base 10 + + size_t start_html_offset, start_fragment_offset; + size_t end_fragment_offset, end_html_offset; + + start_html_offset = html_fragment->length() + + start_html.length() + end_html.length() + + start_fragment.length() + end_fragment.length() + + (has_source_url ? source_url.length() : 0) + + (4*kMaxDigits); + + start_fragment_offset = start_html_offset + start_markup.length(); + end_fragment_offset = start_fragment_offset + markup_utf8.length(); + end_html_offset = end_fragment_offset + end_markup.length(); + + // fill in needed data + start_html.append(StringPrintf("%010u", start_html_offset)); + end_html.append(StringPrintf("%010u", end_html_offset)); + start_fragment.append(StringPrintf("%010u", start_fragment_offset)); + end_fragment.append(StringPrintf("%010u", end_fragment_offset)); + start_markup.append(markup_utf8); + + // create full html_fragment string from the fragments + html_fragment->append(start_html); + html_fragment->append(end_html); + html_fragment->append(start_fragment); + html_fragment->append(end_fragment); + if (has_source_url) + html_fragment->append(source_url); + html_fragment->append(start_markup); + html_fragment->append(end_markup); +} + +// static +void Clipboard::ParseHTMLClipboardFormat(const std::string& html_frag, + std::wstring* markup, + std::string* src_url) { + if (src_url) { + // Obtain SourceURL, if present + std::string src_url_str("SourceURL:"); + size_t line_start = html_frag.find(src_url_str, 0); + if (line_start != std::string::npos) { + size_t src_start = line_start+src_url_str.length(); + size_t src_end = html_frag.find("\n", line_start); + + if (src_end != std::string::npos) + *src_url = html_frag.substr(src_start, src_end - src_start); + } + } + + if (markup) { + // Find the markup between "<!--StartFragment -->" and + // "<!--EndFragment -->", accounting for browser quirks + size_t markup_start = html_frag.find('<', 0); + size_t tag_start = html_frag.find("StartFragment", markup_start); + size_t frag_start = html_frag.find('>', tag_start) + 1; + // Here we do something slightly differently than WebKit. Webkit does a + // forward find for EndFragment, but that seems to be a bug if the html + // fragment actually includes the string "EndFragment" + size_t tag_end = html_frag.rfind("EndFragment", std::string::npos); + size_t frag_end = html_frag.rfind('<', tag_end); + + TrimWhitespace(UTF8ToWide(html_frag.substr(frag_start, + frag_end - frag_start)), + TRIM_ALL, markup); + } +} + +// static +void Clipboard::ParseBookmarkClipboardFormat(const std::wstring& bookmark, + std::wstring* title, + std::string* url) { + const wchar_t* const kDelim = L"\r\n"; + + const size_t title_end = bookmark.find_first_of(kDelim); + if (title) + title->assign(bookmark.substr(0, title_end)); + + if (url) { + const size_t url_start = bookmark.find_first_not_of(kDelim, title_end); + if (url_start != std::wstring::npos) + *url = WideToUTF8(bookmark.substr(url_start, std::wstring::npos)); + } +} diff --git a/base/clipboard.h b/base/clipboard.h new file mode 100644 index 0000000..3599241 --- /dev/null +++ b/base/clipboard.h @@ -0,0 +1,126 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_CLIPBOARD_H__ +#define BASE_CLIPBOARD_H__ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gfx/size.h" +#include "base/shared_memory.h" + +class Clipboard { + public: + Clipboard(); + ~Clipboard(); + + // Clears the clipboard. It is usually a good idea to clear the clipboard + // before writing content to the clipboard. + void Clear() const; + + // Adds UNICODE and ASCII text to the clipboard. + void WriteText(const std::wstring& text) const; + + // Adds HTML to the clipboard. The url parameter is optional, but especially + // useful if the HTML fragment contains relative links + void WriteHTML(const std::wstring& markup, const std::string& src_url) const; + + // Adds a bookmark to the clipboard + void WriteBookmark(const std::wstring& title, const std::string& url) const; + + // Adds both a bookmark and an HTML hyperlink to the clipboard. It is a + // convenience wrapper around WriteBookmark and WriteHTML. + void WriteHyperlink(const std::wstring& title, const std::string& url) const; + + // Adds a bitmap to the clipboard + // This is the slowest way to copy a bitmap to the clipboard as we must first + // memcpy the pixels into GDI and the blit the bitmap to the clipboard. + // Pixel format is assumed to be 32-bit BI_RGB. + void WriteBitmap(const void* pixels, const gfx::Size& size) const; + + // Adds a bitmap to the clipboard + // This function requires read and write access to the bitmap, but does not + // actually modify the shared memory region. + // Pixel format is assumed to be 32-bit BI_RGB. + void WriteBitmapFromSharedMemory(const SharedMemory& bitmap, + const gfx::Size& size) const; + + // Adds a bitmap to the clipboard + // This is the fastest way to copy a bitmap to the clipboard. The HBITMAP + // may either be device-dependent or device-independent. + void WriteBitmapFromHandle(HBITMAP hbitmap, const gfx::Size& size) const; + + // Used by WebKit to determine whether WebKit wrote the clipboard last + void WriteWebSmartPaste() const; + + // Adds a file or group of files to the clipboard. + void WriteFile(const std::wstring& file) const; + void WriteFiles(const std::vector<std::wstring>& files) const; + + // Tests whether the clipboard contains a certain format + bool IsFormatAvailable(unsigned int format) const; + + // Reads UNICODE text from the clipboard, if available. + void ReadText(std::wstring* result) const; + + // Reads ASCII text from the clipboard, if available. + void ReadAsciiText(std::string* result) const; + + // Reads HTML from the clipboard, if available. + void ReadHTML(std::wstring* markup, std::string* src_url) const; + + // Reads a bookmark from the clipboard, if available. + void ReadBookmark(std::wstring* title, std::string* url) const; + + // Reads a file or group of files from the clipboard, if available, into the + // out paramter. + void ReadFile(std::wstring* file) const; + void ReadFiles(std::vector<std::wstring>* files) const; + + private: + static void MarkupToHTMLClipboardFormat(const std::wstring& markup, + const std::string& src_url, + std::string* html_fragment); + + static void ParseHTMLClipboardFormat(const std::string& html_fragment, + std::wstring* markup, + std::string* src_url); + + static void ParseBookmarkClipboardFormat(const std::wstring& bookmark, + std::wstring* title, + std::string* url); + + HWND clipboard_owner_; + + DISALLOW_EVIL_CONSTRUCTORS(Clipboard); +}; + +#endif // BASE_CLIPBOARD_H__ diff --git a/base/clipboard_unittest.cc b/base/clipboard_unittest.cc new file mode 100644 index 0000000..219d0a5 --- /dev/null +++ b/base/clipboard_unittest.cc @@ -0,0 +1,211 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string> + +#include "base/basictypes.h" +#include "base/clipboard.h" +#include "base/clipboard_util.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class ClipboardTest : public testing::Test { + }; +} + +TEST(ClipboardTest, ClearTest) { + Clipboard clipboard; + + clipboard.Clear(); + EXPECT_EQ(false, clipboard.IsFormatAvailable(CF_TEXT)); + EXPECT_EQ(false, clipboard.IsFormatAvailable( + ClipboardUtil::GetHtmlFormat()->cfFormat)); +} + +TEST(ClipboardTest, TextTest) { + Clipboard clipboard; + + std::wstring text(L"This is a wstring!#$"), text_result; + std::string ascii_text; + + clipboard.Clear(); + clipboard.WriteText(text); + EXPECT_EQ(true, clipboard.IsFormatAvailable(CF_UNICODETEXT)); + EXPECT_EQ(true, clipboard.IsFormatAvailable(CF_TEXT)); + clipboard.ReadText(&text_result); + EXPECT_EQ(text, text_result); + clipboard.ReadAsciiText(&ascii_text); + EXPECT_EQ(WideToUTF8(text), ascii_text); +} + +TEST(ClipboardTest, HTMLTest) { + Clipboard clipboard; + + std::wstring markup(L"<strong>Hi!</string>"), markup_result; + std::string url("http://www.example.com/"), url_result; + + clipboard.Clear(); + clipboard.WriteHTML(markup, url); + EXPECT_EQ(true, clipboard.IsFormatAvailable( + ClipboardUtil::GetHtmlFormat()->cfFormat)); + clipboard.ReadHTML(&markup_result, &url_result); + EXPECT_EQ(markup, markup_result); + EXPECT_EQ(url, url_result); +} + +TEST(ClipboardTest, TrickyHTMLTest) { + Clipboard clipboard; + + std::wstring markup(L"<em>Bye!<!--EndFragment --></em>"), markup_result; + std::string url, url_result; + + clipboard.Clear(); + clipboard.WriteHTML(markup, url); + EXPECT_EQ(true, clipboard.IsFormatAvailable( + ClipboardUtil::GetHtmlFormat()->cfFormat)); + clipboard.ReadHTML(&markup_result, &url_result); + EXPECT_EQ(markup, markup_result); + EXPECT_EQ(url, url_result); +} + +TEST(ClipboardTest, BookmarkTest) { + Clipboard clipboard; + + std::wstring title(L"The Example Company"), title_result; + std::string url("http://www.example.com/"), url_result; + + clipboard.Clear(); + clipboard.WriteBookmark(title, url); + EXPECT_EQ(true, + clipboard.IsFormatAvailable(ClipboardUtil::GetUrlWFormat()->cfFormat)); + clipboard.ReadBookmark(&title_result, &url_result); + EXPECT_EQ(title, title_result); + EXPECT_EQ(url, url_result); +} + +TEST(ClipboardTest, HyperlinkTest) { + Clipboard clipboard; + + std::wstring title(L"The Example Company"), title_result; + std::string url("http://www.example.com/"), url_result; + std::wstring html(L"<a href=\"http://www.example.com/\">" + L"The Example Company</a>"), html_result; + + clipboard.Clear(); + clipboard.WriteHyperlink(title, url); + EXPECT_EQ(true, + clipboard.IsFormatAvailable(ClipboardUtil::GetUrlWFormat()->cfFormat)); + EXPECT_EQ(true, + clipboard.IsFormatAvailable(ClipboardUtil::GetHtmlFormat()->cfFormat)); + clipboard.ReadBookmark(&title_result, &url_result); + EXPECT_EQ(title, title_result); + EXPECT_EQ(url, url_result); + clipboard.ReadHTML(&html_result, &url_result); + EXPECT_EQ(html, html_result); + //XXX EXPECT_FALSE(url_result.is_valid()); +} + +TEST(ClipboardTest, MultiFormatTest) { + Clipboard clipboard; + + std::wstring text(L"Hi!"), text_result; + std::wstring markup(L"<strong>Hi!</string>"), markup_result; + std::string url("http://www.example.com/"), url_result; + std::string ascii_text; + + clipboard.Clear(); + clipboard.WriteHTML(markup, url); + clipboard.WriteText(text); + EXPECT_EQ(true, + clipboard.IsFormatAvailable(ClipboardUtil::GetHtmlFormat()->cfFormat)); + EXPECT_EQ(true, clipboard.IsFormatAvailable(CF_UNICODETEXT)); + EXPECT_EQ(true, clipboard.IsFormatAvailable(CF_TEXT)); + clipboard.ReadHTML(&markup_result, &url_result); + EXPECT_EQ(markup, markup_result); + EXPECT_EQ(url, url_result); + clipboard.ReadText(&text_result); + EXPECT_EQ(text, text_result); + clipboard.ReadAsciiText(&ascii_text); + EXPECT_EQ(WideToUTF8(text), ascii_text); +} + +TEST(ClipboardTest, WebSmartPasteTest) { + Clipboard clipboard; + + clipboard.Clear(); + clipboard.WriteWebSmartPaste(); + EXPECT_EQ(true, clipboard.IsFormatAvailable( + ClipboardUtil::GetWebKitSmartPasteFormat()->cfFormat)); +} + +TEST(ClipboardTest, BitmapTest) { + unsigned int fake_bitmap[] = { + 0x46155189, 0xF6A55C8D, 0x79845674, 0xFA57BD89, + 0x78FD46AE, 0x87C64F5A, 0x36EDC5AF, 0x4378F568, + 0x91E9F63A, 0xC31EA14F, 0x69AB32DF, 0x643A3FD1, + }; + + Clipboard clipboard; + + clipboard.Clear(); + clipboard.WriteBitmap(fake_bitmap, gfx::Size(3, 4)); + EXPECT_EQ(true, clipboard.IsFormatAvailable(CF_BITMAP)); +} + +// Files for this test don't actually need to exist on the file system, just +// don't try to use a non-existent file you've retrieved from the clipboard. +TEST(ClipboardTest, FileTest) { + Clipboard clipboard; + clipboard.Clear(); + + std::wstring file = L"C:\\Downloads\\My Downloads\\A Special File.txt"; + clipboard.WriteFile(file); + std::wstring out_file; + clipboard.ReadFile(&out_file); + EXPECT_EQ(file, out_file); +} + +TEST(ClipboardTest, MultipleFilesTest) { + Clipboard clipboard; + clipboard.Clear(); + + std::vector<std::wstring> files; + files.push_back(L"C:\\Downloads\\My Downloads\\File 1.exe"); + files.push_back(L"C:\\Downloads\\My Downloads\\File 2.pdf"); + files.push_back(L"C:\\Downloads\\My Downloads\\File 3.doc"); + clipboard.WriteFiles(files); + + std::vector<std::wstring> out_files; + clipboard.ReadFiles(&out_files); + + EXPECT_EQ(files.size(), out_files.size()); + for (size_t i = 0; i < out_files.size(); ++i) + EXPECT_EQ(files[i], out_files[i]); +} diff --git a/base/clipboard_util.cc b/base/clipboard_util.cc new file mode 100644 index 0000000..3042e76 --- /dev/null +++ b/base/clipboard_util.cc @@ -0,0 +1,400 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/clipboard_util.h" + +#include <shellapi.h> +#include <shlwapi.h> +#include <wininet.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "base/string_util.h" + +namespace { + +bool GetUrlFromHDrop(IDataObject* data_object, std::wstring* url, + std::wstring* title) { + DCHECK(data_object && url && title); + + STGMEDIUM medium; + if (FAILED(data_object->GetData(ClipboardUtil::GetCFHDropFormat(), &medium))) + return false; + + HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); + + if (!hdrop) + return false; + + bool success = false; + wchar_t filename[MAX_PATH]; + if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) { + wchar_t url_buffer[INTERNET_MAX_URL_LENGTH]; + if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") && + GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer, + arraysize(url_buffer), filename)) { + *url = url_buffer; + PathRemoveExtension(filename); + title->assign(PathFindFileName(filename)); + success = true; + } + } + + DragFinish(hdrop); + GlobalUnlock(medium.hGlobal); + // We don't need to call ReleaseStgMedium here because as far as I can tell, + // DragFinish frees the hGlobal for us. + return success; +} + +bool SplitUrlAndTitle(const std::wstring& str, std::wstring* url, + std::wstring* title) { + DCHECK(url && title); + size_t newline_pos = str.find('\n'); + bool success = false; + if (newline_pos != std::string::npos) { + *url = str.substr(0, newline_pos); + title->assign(str.substr(newline_pos + 1)); + success = true; + } else { + *url = str; + title->assign(str); + success = true; + } + return success; +} + +} // namespace + + +FORMATETC* ClipboardUtil::GetUrlFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_INETURLA); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetUrlWFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_INETURLW); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetMozUrlFormat() { + // The format is "URL\nTitle" + static UINT cf = RegisterClipboardFormat(L"text/x-moz-url"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetPlainTextFormat() { + // We don't need to register this format since it's a built in format. + static FORMATETC format = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetPlainTextWFormat() { + // We don't need to register this format since it's a built in format. + static FORMATETC format = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFilenameWFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEW); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFilenameFormat() +{ + static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEA); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetHtmlFormat() { + static UINT cf = RegisterClipboardFormat(L"HTML Format"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetTextHtmlFormat() { + static UINT cf = RegisterClipboardFormat(L"text/html"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetCFHDropFormat() { + // We don't need to register this format since it's a built in format. + static FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFileDescriptorFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFileContentFormatZero() { + static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetWebKitSmartPasteFormat() { + static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL}; + return &format; +} + + +bool ClipboardUtil::HasUrl(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetMozUrlFormat())) || + SUCCEEDED(data_object->QueryGetData(GetUrlWFormat())) || + SUCCEEDED(data_object->QueryGetData(GetUrlFormat())) || + SUCCEEDED(data_object->QueryGetData(GetFilenameWFormat())) || + SUCCEEDED(data_object->QueryGetData(GetFilenameFormat())); +} + +bool ClipboardUtil::HasFilenames(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetCFHDropFormat())); +} + +bool ClipboardUtil::HasPlainText(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetPlainTextWFormat())) || + SUCCEEDED(data_object->QueryGetData(GetPlainTextFormat())); +} + + +bool ClipboardUtil::GetUrl(IDataObject* data_object, + std::wstring* url, std::wstring* title) { + DCHECK(data_object && url && title); + if (!HasUrl(data_object)) + return false; + + // Try to extract a URL from |data_object| in a variety of formats. + STGMEDIUM store; + if (GetUrlFromHDrop(data_object, url, title)) { + return true; + } + + if (SUCCEEDED(data_object->GetData(GetMozUrlFormat(), &store)) || + SUCCEEDED(data_object->GetData(GetUrlWFormat(), &store))) { + // Mozilla URL format or unicode URL + ScopedHGlobal<wchar_t> data(store.hGlobal); + bool success = SplitUrlAndTitle(data.get(), url, title); + ReleaseStgMedium(&store); + if (success) + return true; + } + + if (SUCCEEDED(data_object->GetData(GetUrlFormat(), &store))) { + // URL using ascii + ScopedHGlobal<char> data(store.hGlobal); + bool success = SplitUrlAndTitle(UTF8ToWide(data.get()), url, title); + ReleaseStgMedium(&store); + if (success) + return true; + } + + if (SUCCEEDED(data_object->GetData(GetFilenameWFormat(), &store))) { + // filename using unicode + ScopedHGlobal<wchar_t> data(store.hGlobal); + bool success = false; + if (data.get() && data.get()[0] && (PathFileExists(data.get()) || + PathIsUNC(data.get()))) { + wchar_t file_url[INTERNET_MAX_URL_LENGTH]; + DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]); + if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len, + 0))) { + *url = file_url; + title->assign(file_url); + success = true; + } + } + ReleaseStgMedium(&store); + if (success) + return true; + } + + if (SUCCEEDED(data_object->GetData(GetFilenameFormat(), &store))) { + // filename using ascii + ScopedHGlobal<char> data(store.hGlobal); + bool success = false; + if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) || + PathIsUNCA(data.get()))) { + char file_url[INTERNET_MAX_URL_LENGTH]; + DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]); + if (SUCCEEDED(::UrlCreateFromPathA(data.get(), file_url, &file_url_len, 0))) { + *url = UTF8ToWide(file_url); + title->assign(*url); + success = true; + } + } + ReleaseStgMedium(&store); + if (success) + return true; + } + + return false; +} + +bool ClipboardUtil::GetFilenames(IDataObject* data_object, + std::vector<std::wstring>* filenames) { + DCHECK(data_object && filenames); + if (!HasFilenames(data_object)) + return false; + + STGMEDIUM medium; + if (FAILED(data_object->GetData(GetCFHDropFormat(), &medium))) + return false; + + HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); + if (!hdrop) + return false; + + const int kMaxFilenameLen = 4096; + const unsigned num_files = DragQueryFileW(hdrop, 0xffffffff, 0, 0); + for (unsigned int i = 0; i < num_files; ++i) { + wchar_t filename[kMaxFilenameLen]; + if (!DragQueryFileW(hdrop, i, filename, kMaxFilenameLen)) + continue; + filenames->push_back(filename); + } + + DragFinish(hdrop); + GlobalUnlock(medium.hGlobal); + // We don't need to call ReleaseStgMedium here because as far as I can tell, + // DragFinish frees the hGlobal for us. + return true; +} + +bool ClipboardUtil::GetPlainText(IDataObject* data_object, + std::wstring* plain_text) { + DCHECK(data_object && plain_text); + if (!HasPlainText(data_object)) + return false; + + STGMEDIUM store; + bool success = false; + if (SUCCEEDED(data_object->GetData(GetPlainTextWFormat(), &store))) { + // Unicode text + ScopedHGlobal<wchar_t> data(store.hGlobal); + plain_text->assign(data.get()); + ReleaseStgMedium(&store); + success = true; + } else if (SUCCEEDED(data_object->GetData(GetPlainTextFormat(), &store))) { + // ascii text + ScopedHGlobal<char> data(store.hGlobal); + plain_text->assign(UTF8ToWide(data.get())); + ReleaseStgMedium(&store); + success = true; + } else { + //If a file is dropped on the window, it does not provide either of the + //plain text formats, so here we try to forcibly get a url. + std::wstring title; + success = GetUrl(data_object, plain_text, &title); + } + + return success; +} + +bool ClipboardUtil::GetCFHtml(IDataObject* data_object, + std::wstring* cf_html) { + DCHECK(data_object && cf_html); + if (FAILED(data_object->QueryGetData(GetHtmlFormat()))) + return false; + + STGMEDIUM store; + if (FAILED(data_object->GetData(GetHtmlFormat(), &store))) + return false; + + // MS CF html + ScopedHGlobal<char> data(store.hGlobal); + cf_html->assign(UTF8ToWide(std::string(data.get(), data.Size()))); + ReleaseStgMedium(&store); + return true; +} + +bool ClipboardUtil::GetTextHtml(IDataObject* data_object, + std::wstring* text_html) { + DCHECK(data_object && text_html); + if (FAILED(data_object->QueryGetData(GetTextHtmlFormat()))) + return false; + + STGMEDIUM store; + if (FAILED(data_object->GetData(GetTextHtmlFormat(), &store))) + return false; + + // raw html + ScopedHGlobal<wchar_t> data(store.hGlobal); + text_html->assign(data.get()); + ReleaseStgMedium(&store); + return true; +} + +bool ClipboardUtil::GetFileContents(IDataObject* data_object, + std::wstring* filename, std::string* file_contents) { + DCHECK(data_object && filename && file_contents); + bool has_data = + SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())) || + SUCCEEDED(data_object->QueryGetData(GetFileDescriptorFormat())); + + if (!has_data) + return false; + + STGMEDIUM content; + // The call to GetData can be very slow depending on what is in + // |data_object|. + if (SUCCEEDED(data_object->GetData(GetFileContentFormatZero(), &content))) { + if (TYMED_HGLOBAL == content.tymed) { + ScopedHGlobal<char> data(content.hGlobal); + // The size includes the trailing NULL byte. We don't want it. + file_contents->assign(data.get(), data.Size() - 1); + } + ReleaseStgMedium(&content); + } + + STGMEDIUM description; + if (SUCCEEDED(data_object->GetData(GetFileDescriptorFormat(), + &description))) { + ScopedHGlobal<FILEGROUPDESCRIPTOR> fgd(description.hGlobal); + // We expect there to be at least one file in here. + DCHECK(fgd->cItems >= 1); + filename->assign(fgd->fgd[0].cFileName); + ReleaseStgMedium(&description); + } + return true; +} diff --git a/base/clipboard_util.h b/base/clipboard_util.h new file mode 100644 index 0000000..8daffbb --- /dev/null +++ b/base/clipboard_util.h @@ -0,0 +1,76 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Some helper functions for working with the clipboard and IDataObjects. + +#include <shlobj.h> +#include <string> +#include <vector> + +class ClipboardUtil { + public: + ///////////////////////////////////////////////////////////////////////////// + // Clipboard formats. + static FORMATETC* GetUrlFormat(); + static FORMATETC* GetUrlWFormat(); + static FORMATETC* GetMozUrlFormat(); + static FORMATETC* GetPlainTextFormat(); + static FORMATETC* GetPlainTextWFormat(); + static FORMATETC* GetFilenameFormat(); + static FORMATETC* GetFilenameWFormat(); + // MS HTML Format + static FORMATETC* GetHtmlFormat(); + // Firefox text/html + static FORMATETC* GetTextHtmlFormat(); + static FORMATETC* GetCFHDropFormat(); + static FORMATETC* GetFileDescriptorFormat(); + static FORMATETC* GetFileContentFormatZero(); + static FORMATETC* GetWebKitSmartPasteFormat(); + + ///////////////////////////////////////////////////////////////////////////// + // These methods check to see if |data_object| has the requested type. + // Returns true if it does. + static bool HasUrl(IDataObject* data_object); + static bool HasFilenames(IDataObject* data_object); + static bool HasPlainText(IDataObject* data_object); + + ///////////////////////////////////////////////////////////////////////////// + // Helper methods to extract information from an IDataObject. These methods + // return true if the requested data type is found in |data_object|. + static bool GetUrl(IDataObject* data_object, + std::wstring* url, std::wstring* title); + static bool GetFilenames(IDataObject* data_object, + std::vector<std::wstring>* filenames); + static bool GetPlainText(IDataObject* data_object, std::wstring* plain_text); + static bool GetCFHtml(IDataObject* data_object, std::wstring* cf_html); + static bool GetTextHtml(IDataObject* data_object, std::wstring* text_html); + static bool GetFileContents(IDataObject* data_object, + std::wstring* filename, + std::string* file_contents); +}; diff --git a/base/command_line.cc b/base/command_line.cc new file mode 100644 index 0000000..7755578 --- /dev/null +++ b/base/command_line.cc @@ -0,0 +1,246 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <shellapi.h> + +#include <algorithm> + +#include "base/command_line.h" + +#include "base/logging.h" +#include "base/singleton.h" +#include "base/string_util.h" + +using namespace std; + +// Since we use a lazy match, make sure that longer versions (like L"--") +// are listed before shorter versions (like L"-") of similar prefixes. +const wchar_t* const CommandLine::kSwitchPrefixes[] = {L"--", L"-", L"/"}; + +const wchar_t CommandLine::kSwitchValueSeparator[] = L"="; + +static void Lowercase(wstring* parameter) { + transform(parameter->begin(), parameter->end(), parameter->begin(), tolower); +} + +// CommandLine::Data +// +// This object holds the parsed data for a command line. We hold this in a +// separate object from |CommandLine| so that we can share the parsed data +// across multiple |CommandLine| objects. When we share |Data|, we might be +// accessing this object on multiple threads. To ensure thread safety, the +// public interface of this object is const only. +// +// Do NOT add any non-const methods to this object. You have been warned. +class CommandLine::Data { + public: + Data() { + Init(GetCommandLineW()); + } + + Data(const wstring& command_line) { + Init(command_line); + } + + const std::wstring& command_line_string() const { + return command_line_string_; + } + + const std::wstring& program() const { + return program_; + } + + const std::map<std::wstring, std::wstring>& switches() const { + return switches_; + } + + const std::vector<std::wstring>& loose_values() const { + return loose_values_; + } + + private: + // Returns true if parameter_string represents a switch. If true, + // switch_string and switch_value are set. (If false, both are + // set to the empty string.) + static bool IsSwitch(const wstring& parameter_string, + wstring* switch_string, + wstring* switch_value) { + + *switch_string = L""; + *switch_value = L""; + + for (size_t i = 0; i < arraysize(kSwitchPrefixes); ++i) { + std::wstring prefix(kSwitchPrefixes[i]); + if (parameter_string.find(prefix) != 0) // check prefix + continue; + + const size_t switch_start = prefix.length(); + const size_t equals_position = parameter_string.find( + kSwitchValueSeparator, switch_start); + if (equals_position == wstring::npos) { + *switch_string = parameter_string.substr(switch_start); + } else { + *switch_string = parameter_string.substr( + switch_start, equals_position - switch_start); + *switch_value = parameter_string.substr(equals_position + 1); + } + Lowercase(switch_string); + + return true; + } + + return false; + } + + // Does the actual parsing of the command line. + void Init(const std::wstring& command_line) { + 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); + + // Populate program_ with the trimmed version of the first arg. + TrimWhitespace(args[0], TRIM_ALL, &program_); + + for (int i = 1; i < num_args; ++i) { + wstring arg; + TrimWhitespace(args[i], TRIM_ALL, &arg); + + wstring switch_string; + wstring switch_value; + if (IsSwitch(arg, &switch_string, &switch_value)) { + switches_[switch_string] = switch_value; + } else { + loose_values_.push_back(arg); + } + } + + if (args) + LocalFree(args); + } + + std::wstring command_line_string_; + std::wstring program_; + std::map<std::wstring, std::wstring> switches_; + std::vector<std::wstring> loose_values_; + + DISALLOW_EVIL_CONSTRUCTORS(CommandLine::Data); +}; + +CommandLine::CommandLine() + : we_own_data_(false), // The Singleton class will manage it for us. + data_(Singleton<Data>::get()) { +} + +CommandLine::CommandLine(const wstring& command_line) + : we_own_data_(true), + data_(new Data(command_line)) { +} + +CommandLine::~CommandLine() { + if (we_own_data_) + delete data_; +} + +bool CommandLine::HasSwitch(const wstring& switch_string) const { + wstring lowercased_switch(switch_string); + Lowercase(&lowercased_switch); + return data_->switches().find(lowercased_switch) != data_->switches().end(); +} + +wstring CommandLine::GetSwitchValue(const wstring& switch_string) const { + wstring lowercased_switch(switch_string); + Lowercase(&lowercased_switch); + + const map<wstring, wstring>::const_iterator result = + data_->switches().find(lowercased_switch); + + if (result == data_->switches().end()) { + return L""; + } else { + return result->second; + } +} + +size_t CommandLine::GetLooseValueCount() const { + return data_->loose_values().size(); +} + +CommandLine::LooseValueIterator CommandLine::GetLooseValuesBegin() const { + return data_->loose_values().begin(); +} + +CommandLine::LooseValueIterator CommandLine::GetLooseValuesEnd() const { + return data_->loose_values().end(); +} + +std::wstring CommandLine::command_line_string() const { + return data_->command_line_string(); +} + +std::wstring CommandLine::program() const { + return data_->program(); +} + +// static +void CommandLine::AppendSwitch(wstring* command_line_string, + const wstring& switch_string) { + DCHECK(command_line_string); + command_line_string->append(L" "); + command_line_string->append(kSwitchPrefixes[0]); + command_line_string->append(switch_string); +} + +// static +void CommandLine::AppendSwitchWithValue(wstring* command_line_string, + const wstring& switch_string, + const wstring& value_string) { + AppendSwitch(command_line_string, switch_string); + + if (value_string.empty()) + return; + + command_line_string->append(kSwitchValueSeparator); + // NOTE(jhughes): If the value contains a quotation mark at one + // end but not both, you may get unusable output. + if ((value_string.find(L" ") != std::wstring::npos) && + (value_string[0] != L'"') && + (value_string[value_string.length() - 1] != L'"')) { + // need to provide quotes + StringAppendF(command_line_string, L"\"%s\"", value_string.c_str()); + } else { + command_line_string->append(value_string); + } +} diff --git a/base/command_line.h b/base/command_line.h new file mode 100644 index 0000000..f1b7ade --- /dev/null +++ b/base/command_line.h @@ -0,0 +1,119 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This file contains a class that can be used to extract the salient +// elements of a command line in a relatively lightweight manner. +// Switches can optionally have a value attached using an equals sign, +// as in "-switch=value". Arguments that aren't prefixed with a +// switch prefix are considered "loose parameters". Switch names +// are case-insensitive. + +#ifndef BASE_COMMAND_LINE_H__ +#define BASE_COMMAND_LINE_H__ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" + +class CommandLine { + public: + // Creates a parsed version of the command line used to launch + // the current process. + CommandLine(); + + // Creates a parsed version of the given command-line string. + // The program name is assumed to be the first item in the string. + CommandLine(const std::wstring& command_line); + + ~CommandLine(); + + // Returns true if this command line contains the given switch. + // (Switch names are case-insensitive.) + bool HasSwitch(const std::wstring& 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::wstring GetSwitchValue(const std::wstring& switch_string) const; + + // Returns the number of "loose values" found in the command line. + // Loose values are arguments that aren't switches. + // (The program name is also excluded from the set of loose values.) + size_t GetLooseValueCount() const; + + typedef std::vector<std::wstring>::const_iterator LooseValueIterator; + + // Returns a const_iterator to the list of loose values. + LooseValueIterator GetLooseValuesBegin() const; + + // Returns the end const_iterator for the list of loose values. + LooseValueIterator GetLooseValuesEnd() const; + + // Simply returns the original command line string. + std::wstring command_line_string() const; + + // Returns the program part of the command line string (the first item). + std::wstring program() const; + + // An array containing the prefixes that identify an argument as + // a switch. + static const wchar_t* const kSwitchPrefixes[]; + + // The string that's used to separate switches from their values. + static const wchar_t kSwitchValueSeparator[]; + + // Appends the given switch string (preceded by a space and a switch + // prefix) to the given string. + static void AppendSwitch(std::wstring* command_line_string, + const std::wstring& switch_string); + + // Appends the given switch string (preceded by a space and a switch + // prefix) to the given string, with the given value attached. + static void AppendSwitchWithValue(std::wstring* command_line_string, + const std::wstring& switch_string, + const std::wstring& value_string); + + private: + class Data; + + // True if we are responsible for deleting our |data_| pointer. In some cases + // we cache the result of parsing the command line and |data_|'s lifetime is + // managed by someone else (e.g., the |Singleton| class). + bool we_own_data_; + + // A pointer to the parsed version of the command line. + Data* data_; + + DISALLOW_EVIL_CONSTRUCTORS(CommandLine); +}; + +#endif // BASE_COMMAND_LINE_H__ diff --git a/base/command_line_unittest.cc b/base/command_line_unittest.cc new file mode 100644 index 0000000..dbcf06f --- /dev/null +++ b/base/command_line_unittest.cc @@ -0,0 +1,125 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string> +#include <vector> + +#include "base/command_line.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class CommandLineTest : public testing::Test { + }; +}; + +TEST(CommandLineTest, CommandLineConstructor) { + CommandLine cl(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"\"in the time of submarines...\""); + + EXPECT_FALSE(cl.command_line_string().empty()); + EXPECT_FALSE(cl.HasSwitch(L"cruller")); + EXPECT_FALSE(cl.HasSwitch(L"flim")); + EXPECT_FALSE(cl.HasSwitch(L"program")); + EXPECT_FALSE(cl.HasSwitch(L"dog")); + EXPECT_FALSE(cl.HasSwitch(L"cat")); + EXPECT_FALSE(cl.HasSwitch(L"output-rotation")); + + EXPECT_EQ(L"program", cl.program()); + + EXPECT_TRUE(cl.HasSwitch(L"foo")); + EXPECT_TRUE(cl.HasSwitch(L"bar")); + EXPECT_TRUE(cl.HasSwitch(L"baz")); + EXPECT_TRUE(cl.HasSwitch(L"spaetzle")); + EXPECT_TRUE(cl.HasSwitch(L"SPAETZLE")); + EXPECT_TRUE(cl.HasSwitch(L"other-switches")); + EXPECT_TRUE(cl.HasSwitch(L"input-translation")); + + EXPECT_EQ(L"Crepe", cl.GetSwitchValue(L"spaetzle")); + EXPECT_EQ(L"", cl.GetSwitchValue(L"Foo")); + EXPECT_EQ(L"", cl.GetSwitchValue(L"bar")); + EXPECT_EQ(L"", cl.GetSwitchValue(L"cruller")); + EXPECT_EQ(L"--dog=canine --cat=feline", cl.GetSwitchValue(L"other-switches")); + EXPECT_EQ(L"45--output-rotation", cl.GetSwitchValue(L"input-translation")); + + EXPECT_EQ(3, cl.GetLooseValueCount()); + + CommandLine::LooseValueIterator iter = cl.GetLooseValuesBegin(); + EXPECT_EQ(L"flim", *iter); + ++iter; + EXPECT_EQ(L"flan", *iter); + ++iter; + EXPECT_EQ(L"in the time of submarines...", *iter); + ++iter; + EXPECT_TRUE(iter == cl.GetLooseValuesEnd()); +} + +// These test the command line used to invoke the unit test. +TEST(CommandLineTest, DefaultConstructor) { + CommandLine cl; + EXPECT_FALSE(cl.command_line_string().empty()); + EXPECT_FALSE(cl.program().empty()); +} + +// Tests behavior with an empty input string. +TEST(CommandLineTest, EmptyString) { + CommandLine cl(L""); + EXPECT_TRUE(cl.command_line_string().empty()); + EXPECT_TRUE(cl.program().empty()); + EXPECT_EQ(0, cl.GetLooseValueCount()); +} + +// Test static functions for appending switches to a command line. +TEST(CommandLineTest, AppendSwitches) { + std::wstring cl_string = L"Program"; + std::wstring switch1 = L"switch1"; + std::wstring switch2 = L"switch2"; + std::wstring value = L"value"; + std::wstring switch3 = L"switch3"; + std::wstring value3 = L"a value with spaces"; + std::wstring switch4 = L"switch4"; + std::wstring value4 = L"\"a value with quotes\""; + + CommandLine::AppendSwitch(&cl_string, switch1); + CommandLine::AppendSwitchWithValue(&cl_string, switch2, value); + CommandLine::AppendSwitchWithValue(&cl_string, switch3, value3); + CommandLine::AppendSwitchWithValue(&cl_string, switch4, value4); + CommandLine cl(cl_string); + + EXPECT_TRUE(cl.HasSwitch(switch1)); + EXPECT_TRUE(cl.HasSwitch(switch2)); + EXPECT_EQ(value, cl.GetSwitchValue(switch2)); + EXPECT_TRUE(cl.HasSwitch(switch3)); + EXPECT_EQ(value3, cl.GetSwitchValue(switch3)); + EXPECT_TRUE(cl.HasSwitch(switch2)); + EXPECT_EQ(value4.substr(1, value4.length() - 2), cl.GetSwitchValue(switch4)); +} diff --git a/base/condition_variable.cc b/base/condition_variable.cc new file mode 100644 index 0000000..0e4f7c8 --- /dev/null +++ b/base/condition_variable.cc @@ -0,0 +1,464 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/condition_variable.h" + +#include <stack> + +#include "base/lock.h" +#include "base/logging.h" + +ConditionVariable::ConditionVariable(Lock* user_lock) + : user_lock_(*user_lock), + run_state_(RUNNING), + allocation_counter_(0), + recycling_list_size_(0) { + DCHECK(user_lock); +} + +ConditionVariable::~ConditionVariable() { + 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_); +} + +// Wait() atomically releases the caller's lock as it starts to Wait, and then +// reacquires it when it is signaled. +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + Event* waiting_event; + HANDLE handle; + { + AutoLock auto_lock(internal_lock_); + if (RUNNING != run_state_) return; // Destruction in progress. + waiting_event = GetEventForWaiting(); + handle = waiting_event->handle(); + DCHECK(handle); + } // Release internal_lock. + + { + AutoUnlock unlock(user_lock_); // Release caller's lock + WaitForSingleObject(handle, static_cast<DWORD>(max_time.InMilliseconds())); + // Minimize spurious signal creation window by recycling asap. + AutoLock auto_lock(internal_lock_); + RecycleEvent(waiting_event); + // Release internal_lock_ + } // Reacquire callers lock to depth at entry. +} + +// Broadcast() is guaranteed to signal all threads that were waiting (i.e., had +// a cv_event internally allocated for them) before Broadcast() was called. +void ConditionVariable::Broadcast() { + std::stack<HANDLE> handles; // See FAQ-question-10. + { + AutoLock auto_lock(internal_lock_); + if (waiting_list_.IsEmpty()) + return; + while (!waiting_list_.IsEmpty()) + // This is not a leak from waiting_list_. See FAQ-question 12. + handles.push(waiting_list_.PopBack()->handle()); + } // Release internal_lock_. + while (!handles.empty()) { + SetEvent(handles.top()); + handles.pop(); + } +} + +// Signal() will select one of the waiting threads, and signal it (signal its +// cv_event). For better performance we signal the thread that went to sleep +// most recently (LIFO). If we want fairness, then we wake the thread that has +// been sleeping the longest (FIFO). +void ConditionVariable::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. +ConditionVariable::Event* ConditionVariable::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_++; + // CHECK_NE is not defined in our codebase, so we have to use CHECK + CHECK(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 ConditionVariable::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. + +ConditionVariable::Event::Event() : handle_(0) { + next_ = prev_ = this; // Self referencing circular. +} + +ConditionVariable::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 ConditionVariable::Event::InitListElement() { + DCHECK(!handle_); + handle_ = CreateEvent(NULL, false, false, NULL); + CHECK(handle_); +} + +// Methods for use on lists. +bool ConditionVariable::Event::IsEmpty() const { + DCHECK(ValidateAsList()); + return IsSingleton(); +} + +void ConditionVariable::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)); +} + +ConditionVariable::Event* ConditionVariable::Event::PopFront() { + DCHECK(ValidateAsList()); + DCHECK(!IsSingleton()); + return next_->Extract(); +} + +ConditionVariable::Event* ConditionVariable::Event::PopBack() { + DCHECK(ValidateAsList()); + DCHECK(!IsSingleton()); + return prev_->Extract(); +} + +// Methods for use on list elements. +// Accessor method. +HANDLE ConditionVariable::Event::handle() const { + DCHECK(ValidateAsItem()); + return handle_; +} + +// Pull an element from a list (if it's in one). +ConditionVariable::Event* ConditionVariable::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 ConditionVariable::Event::IsSingleton() const { + DCHECK(ValidateLinks()); + return next_ == this; +} + +// Provide pre/post conditions to validate correct manipulations. +bool ConditionVariable::Event::ValidateAsDistinct(Event* other) const { + return ValidateLinks() && other->ValidateLinks() && (this != other); +} + +bool ConditionVariable::Event::ValidateAsItem() const { + return (0 != handle_) && ValidateLinks(); +} + +bool ConditionVariable::Event::ValidateAsList() const { + return (0 == handle_) && ValidateLinks(); +} + +bool ConditionVariable::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 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. + +*/ diff --git a/base/condition_variable.h b/base/condition_variable.h new file mode 100644 index 0000000..bdb9a19 --- /dev/null +++ b/base/condition_variable.h @@ -0,0 +1,195 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ConditionVariable is a reasonable attempt at simulating +// the newer Posix and Vista-only construct for condition variable +// synchronization. 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 +// 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.cc. + +#ifndef BASE_CONDITION_VARIABLE_H__ +#define BASE_CONDITION_VARIABLE_H__ + +#include "base/lock.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/time.h" + +class Lock; + +class 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 TimedWait(const TimeDelta& max_time); + void 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)); + } + + // Broadcast() revives all waiting threads. + void Broadcast(); + // Signal() revives one waiting thread. + void Signal(); + + private: + // 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_EVIL_CONSTRUCTORS(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. + Lock internal_lock_; + // Lock that is acquired before calling Wait(). + 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_; + + DISALLOW_EVIL_CONSTRUCTORS(ConditionVariable); +}; + +#endif // BASE_CONDITION_VARIABLE_H__ diff --git a/base/condition_variable_test.cc b/base/condition_variable_test.cc new file mode 100644 index 0000000..35efa5f --- /dev/null +++ b/base/condition_variable_test.cc @@ -0,0 +1,707 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Multi-threaded tests of ConditionVariable class. + +#include <time.h> +#include <algorithm> +#include <vector> + +#include "base/check_handler.h" +#include "base/condition_variable.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/spin_wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +//------------------------------------------------------------------------------ +// Define our test class, with several common variables. +//------------------------------------------------------------------------------ + +class ConditionVariableTest : public testing::Test { + public: + const TimeDelta kZeroMs; + const TimeDelta kTenMs; + const TimeDelta kThirtyMs; + const TimeDelta kFortyFiveMs; + const TimeDelta kSixtyMs; + const TimeDelta kOneHundredMs; + + explicit ConditionVariableTest() + : kZeroMs(TimeDelta::FromMilliseconds(0)), + kTenMs(TimeDelta::FromMilliseconds(10)), + kThirtyMs(TimeDelta::FromMilliseconds(30)), + kFortyFiveMs(TimeDelta::FromMilliseconds(45)), + kSixtyMs(TimeDelta::FromMilliseconds(60)), + kOneHundredMs(TimeDelta::FromMilliseconds(100)) { + } +}; + +//------------------------------------------------------------------------------ +// Define a class that will control activities an several multi-threaded tests. +// The general structure of multi-threaded tests is that a test case will +// construct an instance of a WorkQueue. The WorkQueue will spin up some +// threads and control them thoughout their lifetime, as well as maintaining +// a central respository 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. +//------------------------------------------------------------------------------ +// Forward declare the WorkerProcess task +static DWORD WINAPI WorkerProcess(void* p); + +// 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: + explicit WorkQueue(int thread_count); + ~WorkQueue(); + + //---------------------------------------------------------------------------- + // 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. + int shutdown_task_count() const; + + void thread_shutting_down(); + 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; + + void SetWorkTime(TimeDelta delay); + void SetTaskCount(int count); + void SetAllowHelp(bool allow); + + void SetShutdown(); + + 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_; + scoped_array<HANDLE> handles_; + std::vector<int> assignment_history_; // Number of assignment per worker. + std::vector<int> completion_history_; // Number of completions per worker. + int thread_started_counter_; // Used to issue unique id to workers. + int shutdown_task_count_; // Number of tasks told to shutdown + int task_count_; // Number of assignment tasks waiting to be processed. + TimeDelta worker_delay_; // Time each task takes to complete. + bool allow_help_requests_; // Workers can signal more workers. + bool shutdown_; // Set when threads need to terminate. +}; + +//------------------------------------------------------------------------------ +// 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). + +// WorkerProcess() implements the above task for all threads. +// It calls the controlling object to tell the creator about progress, and to +// ask about tasks. +static DWORD WINAPI WorkerProcess(void* p) { + int thread_id; + class WorkQueue* queue = reinterpret_cast<WorkQueue*>(p); + { + AutoLock auto_lock(*queue->lock()); + thread_id = queue->GetThreadId(); + if (queue->EveryIdWasAllocated()) + queue->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; + { + AutoLock auto_lock(*queue->lock()); + while (0 == queue->task_count() && !queue->shutdown()) { + queue->work_is_available()->Wait(); + } + if (queue->shutdown()) { + // Ack the notification of a shutdown message back to the controller. + queue->thread_shutting_down(); + return 0; // Terminate. + } + // Get our task duration from the queue. + work_time = queue->GetAnAssignment(thread_id); + could_use_help = (queue->task_count() > 0) && + queue->allow_help_requests(); + } // Release lock + + // Do work (outside of locked region. + if (could_use_help) + queue->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. + AutoLock auto_lock(private_lock); + ConditionVariable private_cv(&private_lock); + private_cv.TimedWait(work_time); // Unsynchronized waiting. + } + + { + AutoLock auto_lock(*queue->lock()); + // Send notification that we completed our "work." + queue->WorkIsCompleted(thread_id); + } + } +} +//------------------------------------------------------------------------------ +// 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, LockedExpressionTest) { + int i = 0; + Lock lock; + + // Old LOCKED_EXPRESSION macro caused syntax errors here. + // ... yes... compiler will optimize this example. + // Syntax error is what I'm after precluding. + if (0) + LOCKED_EXPRESSION(lock, i = 1); + else + LOCKED_EXPRESSION(lock, i = 2); + + EXPECT_EQ(2, i); +} + +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(); +} + +TEST_F(ConditionVariableTest, MultiThreadConsumerTest) { + const int kThreadCount = 10; + WorkQueue queue(kThreadCount); // Start the threads. + + Lock private_lock; // Used locally for master to wait. + AutoLock private_held_lock(private_lock); + ConditionVariable private_cv(&private_lock); + + { + 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. + private_cv.TimedWait(kTenMs); + + { + // Since we have no tasks, all threads should be waiting by now. + 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 one worker do 3 30ms tasks. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(false); + } + queue.work_is_available()->Signal(); // Start up one thread. + // Wait to allow solo worker insufficient time to get done. + private_cv.TimedWait(kFortyFiveMs); // Should take about 90 ms. + + { + // Check that all work HASN'T completed yet. + AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(1, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(1, queue.GetNumThreadsCompletingTasks()); + EXPECT_GT(2, queue.task_count()); // 2 should have started. + EXPECT_GT(3, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(1, queue.GetNumberOfCompletedTasks()); + } + // Wait to allow solo workers to get done. + private_cv.TimedWait(kSixtyMs); // Should take about 45ms more. + + { + // Check that all work was done by one thread id. + AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(1, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(1, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(3, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(3, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task include getting help from another worker. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait to allow the 3 workers to get done. + private_cv.TimedWait(kFortyFiveMs); // Should take about 30 ms. + + { + 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()); + + // 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 to allow the 3 workers to get done. + private_cv.TimedWait(kFortyFiveMs); + + { + 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(); // We already signal all threads. + // Wait to allow the 3 workers to get done. + private_cv.TimedWait(kOneHundredMs); + + { + 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); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait to allow the 10 workers to get done. + private_cv.TimedWait(kOneHundredMs); // Should take about 60 ms. + + { + AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(2, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(2, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(20, queue.GetNumberOfCompletedTasks()); + + // Same as last test, but with Broadcast(). + queue.ResetHistory(); + queue.SetTaskCount(20); // 2 tasks per process. + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Broadcast(); + // Wait to allow the 10 workers to get done. + private_cv.TimedWait(kOneHundredMs); // Should take about 60 ms. + + { + AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(2, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(2, queue.GetMinCompletionsByWorkerThread()); + 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.shutdown_task_count() == kThreadCount); + Sleep(10); // Be sure they're all shutdown. +} + +TEST_F(ConditionVariableTest, LargeFastTaskTest) { + const int kThreadCount = 200; + WorkQueue queue(kThreadCount); // Start the threads. + + Lock private_lock; // Used locally for master to wait. + AutoLock private_held_lock(private_lock); + ConditionVariable private_cv(&private_lock); + + { + 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. + private_cv.TimedWait(kThirtyMs); + + { + // Since we have no tasks, all threads should be waiting by now. + 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. + { + AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + // Don't bother to use locks: We may not get info in time... but we'll see it + // eventually. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + 20 * kThreadCount == + queue.GetNumberOfCompletedTasks()); + + { + // With Broadcast(), every thread should have participated. + // but with racing.. they may not all have done equal numbers of tasks. + 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 + { + AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + // Don't bother to use locks: We may not get info in time... but we'll see it + // eventually. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + 4 * kThreadCount == + queue.GetNumberOfCompletedTasks()); + + { + // With Signal(), every thread should have participated. + // but with racing.. they may not all have done four tasks. + 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 shutdows to complete. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + queue.shutdown_task_count() == kThreadCount); + Sleep(10); // Be sure they're all shutdown. +} + +//------------------------------------------------------------------------------ +// 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), + handles_(new HANDLE[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) { + handles_[i] = CreateThread(NULL, // security. + 0, // <64K stack size. + WorkerProcess, // Static function. + reinterpret_cast<void*>(this), + 0, // Create running process. + NULL); // OS version of thread id. + EXPECT_NE(reinterpret_cast<void*>(NULL), handles_[i]); + } +} + +WorkQueue::~WorkQueue() { + { + AutoLock auto_lock(lock_); + SetShutdown(); + } + work_is_available_.Broadcast(); // Tell them all to terminate. + DWORD result = WaitForMultipleObjects( + thread_count_, + &handles_[0], + true, // Wait for all + 10000); // Ten seconds max. + + for (int i = 0; i < thread_count_; ++i) { + int ret_value = CloseHandle(handles_[i]); + CHECK(ret_value); + handles_[i] = NULL; + } +} + +int WorkQueue::GetThreadId() { + DCHECK(!EveryIdWasAllocated()); + return thread_started_counter_++; // Give out Unique IDs. +} + +bool WorkQueue::EveryIdWasAllocated() const { + return thread_count_ == thread_started_counter_; +} + +TimeDelta WorkQueue::GetAnAssignment(int thread_id) { + 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) { + completion_history_[thread_id]++; +} + +int WorkQueue::task_count() const { + return task_count_; +} + +bool WorkQueue::allow_help_requests() const { + return allow_help_requests_; +} + +bool WorkQueue::shutdown() const { + return shutdown_; +} + +int WorkQueue::shutdown_task_count() const { + return shutdown_task_count_; +} + +void WorkQueue::thread_shutting_down() { + 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; +} + +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() { + shutdown_ = true; +} + +} // namespace diff --git a/base/data/file_util_unittest/binary_file.bin b/base/data/file_util_unittest/binary_file.bin Binary files differnew file mode 100644 index 0000000..f53cc82 --- /dev/null +++ b/base/data/file_util_unittest/binary_file.bin diff --git a/base/data/file_util_unittest/binary_file_diff.bin b/base/data/file_util_unittest/binary_file_diff.bin Binary files differnew file mode 100644 index 0000000..103b26d --- /dev/null +++ b/base/data/file_util_unittest/binary_file_diff.bin diff --git a/base/data/file_util_unittest/binary_file_same.bin b/base/data/file_util_unittest/binary_file_same.bin Binary files differnew file mode 100644 index 0000000..f53cc82 --- /dev/null +++ b/base/data/file_util_unittest/binary_file_same.bin diff --git a/base/data/file_util_unittest/different.txt b/base/data/file_util_unittest/different.txt new file mode 100644 index 0000000..5b9f9c4 --- /dev/null +++ b/base/data/file_util_unittest/different.txt @@ -0,0 +1 @@ +This file is different. diff --git a/base/data/file_util_unittest/different_first.txt b/base/data/file_util_unittest/different_first.txt new file mode 100644 index 0000000..8661d66 --- /dev/null +++ b/base/data/file_util_unittest/different_first.txt @@ -0,0 +1 @@ +this file is the same. diff --git a/base/data/file_util_unittest/different_last.txt b/base/data/file_util_unittest/different_last.txt new file mode 100644 index 0000000..e8b3e5a --- /dev/null +++ b/base/data/file_util_unittest/different_last.txt @@ -0,0 +1 @@ +This file is the same.
\ No newline at end of file diff --git a/base/data/file_util_unittest/empty1.txt b/base/data/file_util_unittest/empty1.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/base/data/file_util_unittest/empty1.txt diff --git a/base/data/file_util_unittest/empty2.txt b/base/data/file_util_unittest/empty2.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/base/data/file_util_unittest/empty2.txt diff --git a/base/data/file_util_unittest/original.txt b/base/data/file_util_unittest/original.txt new file mode 100644 index 0000000..4422f57 --- /dev/null +++ b/base/data/file_util_unittest/original.txt @@ -0,0 +1 @@ +This file is the same. diff --git a/base/data/file_util_unittest/same.txt b/base/data/file_util_unittest/same.txt new file mode 100644 index 0000000..4422f57 --- /dev/null +++ b/base/data/file_util_unittest/same.txt @@ -0,0 +1 @@ +This file is the same. diff --git a/base/data/file_util_unittest/same_length.txt b/base/data/file_util_unittest/same_length.txt new file mode 100644 index 0000000..157405c --- /dev/null +++ b/base/data/file_util_unittest/same_length.txt @@ -0,0 +1 @@ +This file is not same. diff --git a/base/data/file_util_unittest/shortened.txt b/base/data/file_util_unittest/shortened.txt new file mode 100644 index 0000000..2bee82c --- /dev/null +++ b/base/data/file_util_unittest/shortened.txt @@ -0,0 +1 @@ +This file is the
\ No newline at end of file diff --git a/base/data/file_version_info_unittest/FileVersionInfoTest1.dll b/base/data/file_version_info_unittest/FileVersionInfoTest1.dll Binary files differnew file mode 100644 index 0000000..bdf8dc0 --- /dev/null +++ b/base/data/file_version_info_unittest/FileVersionInfoTest1.dll diff --git a/base/data/file_version_info_unittest/FileVersionInfoTest2.dll b/base/data/file_version_info_unittest/FileVersionInfoTest2.dll Binary files differnew file mode 100644 index 0000000..51e7966 --- /dev/null +++ b/base/data/file_version_info_unittest/FileVersionInfoTest2.dll diff --git a/base/data/purify/base_unittests.exe.gtest.txt b/base/data/purify/base_unittests.exe.gtest.txt new file mode 100644 index 0000000..0687e7e --- /dev/null +++ b/base/data/purify/base_unittests.exe.gtest.txt @@ -0,0 +1,19 @@ +# this test causes Purify to get completely confused, aborting the test and +# popping up 10 or more error dialogs +StatsTableTest.MultipleProcesses + +# see bug 1151158 +# causes purify to occasionally crash, possibly the same reason as 1110206 below +StatsTableTest.MultipleThreads + +# this test takes a really long time to run in Purify +TimeTicks.Rollover + +# see bug 1110206 +ConditionVariableTest.LargeFastTaskTest + +# see bug 1150075 +MessageLoopTest.Crasher* + +# see bug 1195707 +WMIUtilTest.* diff --git a/base/data/purify/base_unittests.exe_MLK.txt b/base/data/purify/base_unittests.exe_MLK.txt new file mode 100644 index 0000000..e622502 --- /dev/null +++ b/base/data/purify/base_unittests.exe_MLK.txt @@ -0,0 +1,97 @@ +std::D::_Allocate(unsigned int,char *) [base_unittests.exe] +Alloc Location + ... + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::_Mutex::_Mutex(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::ios_base::_Init(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::D::_Allocate(unsigned int,char *) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::~LogMessage(void) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::D::_Allocate(unsigned int,char *) [base_unittests.exe] +Alloc Location + ... + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::_Mutex::_Mutex(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::ios_base::_Init(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::D::_Allocate(unsigned int,char *) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::~LogMessage(void) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +CoTaskMemAlloc [OLE32.DLL] +Alloc Location + ... + base/wmi_util.cc WMIUtil::CreateLocalConnection(bool) + base/wmi_util.cc WMIProcessUtil::Launch(class std::basic_string const &,int *) + base/wmi_util_unittest.cc WMIUtilTest_TestLaunchProcess_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + diff --git a/base/data/purify/base_unittests.exe_MLK_flakey.txt b/base/data/purify/base_unittests.exe_MLK_flakey.txt new file mode 100644 index 0000000..0626ba0 --- /dev/null +++ b/base/data/purify/base_unittests.exe_MLK_flakey.txt @@ -0,0 +1,8 @@ +CoTaskMemAlloc [OLE32.DLL] +Alloc Location + ... + base/wmi_util.cc WMIUtil::CreateLocalConnection(bool) + base/wmi_util.cc WMIProcessUtil::Launch(class std::basic_string const &,int *) + base/wmi_util_unittest.cc WMIUtilTest_TestLaunchProcess_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ diff --git a/base/data/purify/base_unittests.exe_MLK_ignore.txt b/base/data/purify/base_unittests.exe_MLK_ignore.txt new file mode 100644 index 0000000..020fadb --- /dev/null +++ b/base/data/purify/base_unittests.exe_MLK_ignore.txt @@ -0,0 +1,93 @@ +# ----- +# Leaks in ::RaiseException, called when we log a fatal error. See bug 1078612. + +std::strstreambuf::overflow(int) [base_unittests.exe] +Alloc Location + ... + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::_Mutex::_Mutex(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::ios_base::_Init(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::D::_Allocate(unsigned int,char *) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::~LogMessage(void) + base/check_handler_unittest.cc ThisFunctionAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::strstreambuf::overflow(int) [base_unittests.exe] +Alloc Location + ... + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::_Mutex::_Mutex(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::ios_base::_Init(void) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::LogMessage(char const*,int,int) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +std::D::_Allocate(unsigned int,char *) [base_unittests.exe] +Alloc Location + ... + base/logging.cc logging::LogMessage::~LogMessage(void) + base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void) + base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + +# End of leaks in ::RaiseException +# ----- diff --git a/base/data/purify/base_unittests.exe_PAR_ignore.txt b/base/data/purify/base_unittests.exe_PAR_ignore.txt new file mode 100644 index 0000000..69018f2 --- /dev/null +++ b/base/data/purify/base_unittests.exe_PAR_ignore.txt @@ -0,0 +1,8 @@ +# Probably a Purify error. See bug 1076843. +WideCharToMultiByte: Invalid size (0x27) for destination buffer. +Error Location + ... + base/file_util_unittest.cc FileUtilTest_ResolveShortcutTest_Test::TestBody(void) + testing/gtest/src/gtest.cc testing::Test::Run(void) + ^^^ + diff --git a/base/data/purify/base_unittests.exe_UMR.txt b/base/data/purify/base_unittests.exe_UMR.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/base/data/purify/base_unittests.exe_UMR.txt diff --git a/base/data/vectorcanvastest/basicdrawing/00_pc_clean.png b/base/data/vectorcanvastest/basicdrawing/00_pc_clean.png Binary files differnew file mode 100644 index 0000000..a5435f2 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/00_pc_clean.png diff --git a/base/data/vectorcanvastest/basicdrawing/00_vc_clean.png b/base/data/vectorcanvastest/basicdrawing/00_vc_clean.png Binary files differnew file mode 100644 index 0000000..a5435f2 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/00_vc_clean.png diff --git a/base/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png b/base/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png Binary files differnew file mode 100644 index 0000000..a5435f2 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png diff --git a/base/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png b/base/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png Binary files differnew file mode 100644 index 0000000..a5435f2 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png diff --git a/base/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png b/base/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png Binary files differnew file mode 100644 index 0000000..c21fdf1 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png diff --git a/base/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png b/base/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png Binary files differnew file mode 100644 index 0000000..c21fdf1 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png diff --git a/base/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png b/base/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png Binary files differnew file mode 100644 index 0000000..dfc46a8 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png diff --git a/base/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png b/base/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png Binary files differnew file mode 100644 index 0000000..dfc46a8 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png diff --git a/base/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png b/base/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000..dfc46a8 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png diff --git a/base/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png b/base/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000..dfc46a8 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png diff --git a/base/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png b/base/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000..69cc6dc --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png diff --git a/base/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png b/base/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000..69cc6dc --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png diff --git a/base/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png b/base/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png Binary files differnew file mode 100644 index 0000000..9cbff6e --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png diff --git a/base/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png b/base/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png Binary files differnew file mode 100644 index 0000000..9cbff6e --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png diff --git a/base/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png b/base/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png Binary files differnew file mode 100644 index 0000000..bbdfc36 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png diff --git a/base/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png b/base/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png Binary files differnew file mode 100644 index 0000000..bbdfc36 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png diff --git a/base/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png b/base/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png Binary files differnew file mode 100644 index 0000000..9dc35f0 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png diff --git a/base/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png b/base/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png Binary files differnew file mode 100644 index 0000000..9dc35f0 --- /dev/null +++ b/base/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png diff --git a/base/data/vectorcanvastest/bitmaps/00_pc_opaque.png b/base/data/vectorcanvastest/bitmaps/00_pc_opaque.png Binary files differnew file mode 100644 index 0000000..812b1ca --- /dev/null +++ b/base/data/vectorcanvastest/bitmaps/00_pc_opaque.png diff --git a/base/data/vectorcanvastest/bitmaps/00_vc_opaque.png b/base/data/vectorcanvastest/bitmaps/00_vc_opaque.png Binary files differnew file mode 100644 index 0000000..812b1ca --- /dev/null +++ b/base/data/vectorcanvastest/bitmaps/00_vc_opaque.png diff --git a/base/data/vectorcanvastest/bitmaps/01_pc_alpha.png b/base/data/vectorcanvastest/bitmaps/01_pc_alpha.png Binary files differnew file mode 100644 index 0000000..1d1342b --- /dev/null +++ b/base/data/vectorcanvastest/bitmaps/01_pc_alpha.png diff --git a/base/data/vectorcanvastest/bitmaps/01_vc_alpha.png b/base/data/vectorcanvastest/bitmaps/01_vc_alpha.png Binary files differnew file mode 100644 index 0000000..1d1342b --- /dev/null +++ b/base/data/vectorcanvastest/bitmaps/01_vc_alpha.png diff --git a/base/data/vectorcanvastest/bitmaps/bitmap_alpha.png b/base/data/vectorcanvastest/bitmaps/bitmap_alpha.png Binary files differnew file mode 100644 index 0000000..a19d09d --- /dev/null +++ b/base/data/vectorcanvastest/bitmaps/bitmap_alpha.png diff --git a/base/data/vectorcanvastest/bitmaps/bitmap_opaque.png b/base/data/vectorcanvastest/bitmaps/bitmap_opaque.png Binary files differnew file mode 100644 index 0000000..3560d27 --- /dev/null +++ b/base/data/vectorcanvastest/bitmaps/bitmap_opaque.png diff --git a/base/data/vectorcanvastest/circles/00_pc_circle_stroke.png b/base/data/vectorcanvastest/circles/00_pc_circle_stroke.png Binary files differnew file mode 100644 index 0000000..896631b --- /dev/null +++ b/base/data/vectorcanvastest/circles/00_pc_circle_stroke.png diff --git a/base/data/vectorcanvastest/circles/00_vc_circle_stroke.png b/base/data/vectorcanvastest/circles/00_vc_circle_stroke.png Binary files differnew file mode 100644 index 0000000..d1d4cb2 --- /dev/null +++ b/base/data/vectorcanvastest/circles/00_vc_circle_stroke.png diff --git a/base/data/vectorcanvastest/circles/01_pc_circle_fill.png b/base/data/vectorcanvastest/circles/01_pc_circle_fill.png Binary files differnew file mode 100644 index 0000000..a317292 --- /dev/null +++ b/base/data/vectorcanvastest/circles/01_pc_circle_fill.png diff --git a/base/data/vectorcanvastest/circles/01_vc_circle_fill.png b/base/data/vectorcanvastest/circles/01_vc_circle_fill.png Binary files differnew file mode 100644 index 0000000..0628c02 --- /dev/null +++ b/base/data/vectorcanvastest/circles/01_vc_circle_fill.png diff --git a/base/data/vectorcanvastest/circles/02_pc_circle_over_strike.png b/base/data/vectorcanvastest/circles/02_pc_circle_over_strike.png Binary files differnew file mode 100644 index 0000000..64ae06a --- /dev/null +++ b/base/data/vectorcanvastest/circles/02_pc_circle_over_strike.png diff --git a/base/data/vectorcanvastest/circles/02_vc_circle_over_strike.png b/base/data/vectorcanvastest/circles/02_vc_circle_over_strike.png Binary files differnew file mode 100644 index 0000000..f333f9d --- /dev/null +++ b/base/data/vectorcanvastest/circles/02_vc_circle_over_strike.png diff --git a/base/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png b/base/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png Binary files differnew file mode 100644 index 0000000..66d6a33 --- /dev/null +++ b/base/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png diff --git a/base/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png b/base/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png Binary files differnew file mode 100644 index 0000000..a756cf3 --- /dev/null +++ b/base/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png diff --git a/base/data/vectorcanvastest/circles/04_pc_mixed_stroke.png b/base/data/vectorcanvastest/circles/04_pc_mixed_stroke.png Binary files differnew file mode 100644 index 0000000..de9c492 --- /dev/null +++ b/base/data/vectorcanvastest/circles/04_pc_mixed_stroke.png diff --git a/base/data/vectorcanvastest/circles/04_vc_mixed_stroke.png b/base/data/vectorcanvastest/circles/04_vc_mixed_stroke.png Binary files differnew file mode 100644 index 0000000..ae3cd03 --- /dev/null +++ b/base/data/vectorcanvastest/circles/04_vc_mixed_stroke.png diff --git a/base/data/vectorcanvastest/clippingclean/00_pc_clipped.png b/base/data/vectorcanvastest/clippingclean/00_pc_clipped.png Binary files differnew file mode 100644 index 0000000..14ff949 --- /dev/null +++ b/base/data/vectorcanvastest/clippingclean/00_pc_clipped.png diff --git a/base/data/vectorcanvastest/clippingclean/00_vc_clipped.png b/base/data/vectorcanvastest/clippingclean/00_vc_clipped.png Binary files differnew file mode 100644 index 0000000..14ff949 --- /dev/null +++ b/base/data/vectorcanvastest/clippingclean/00_vc_clipped.png diff --git a/base/data/vectorcanvastest/clippingclean/01_pc_unclipped.png b/base/data/vectorcanvastest/clippingclean/01_pc_unclipped.png Binary files differnew file mode 100644 index 0000000..436f9a5 --- /dev/null +++ b/base/data/vectorcanvastest/clippingclean/01_pc_unclipped.png diff --git a/base/data/vectorcanvastest/clippingclean/01_vc_unclipped.png b/base/data/vectorcanvastest/clippingclean/01_vc_unclipped.png Binary files differnew file mode 100644 index 0000000..436f9a5 --- /dev/null +++ b/base/data/vectorcanvastest/clippingclean/01_vc_unclipped.png diff --git a/base/data/vectorcanvastest/clippingcombined/00_pc_combined.png b/base/data/vectorcanvastest/clippingcombined/00_pc_combined.png Binary files differnew file mode 100644 index 0000000..14ff949 --- /dev/null +++ b/base/data/vectorcanvastest/clippingcombined/00_pc_combined.png diff --git a/base/data/vectorcanvastest/clippingcombined/00_vc_combined.png b/base/data/vectorcanvastest/clippingcombined/00_vc_combined.png Binary files differnew file mode 100644 index 0000000..14ff949 --- /dev/null +++ b/base/data/vectorcanvastest/clippingcombined/00_vc_combined.png diff --git a/base/data/vectorcanvastest/clippingintersect/00_pc_intersect.png b/base/data/vectorcanvastest/clippingintersect/00_pc_intersect.png Binary files differnew file mode 100644 index 0000000..704f03a --- /dev/null +++ b/base/data/vectorcanvastest/clippingintersect/00_pc_intersect.png diff --git a/base/data/vectorcanvastest/clippingintersect/00_vc_intersect.png b/base/data/vectorcanvastest/clippingintersect/00_vc_intersect.png Binary files differnew file mode 100644 index 0000000..704f03a --- /dev/null +++ b/base/data/vectorcanvastest/clippingintersect/00_vc_intersect.png diff --git a/base/data/vectorcanvastest/clippingpath/00_pc_path.png b/base/data/vectorcanvastest/clippingpath/00_pc_path.png Binary files differnew file mode 100644 index 0000000..78443af --- /dev/null +++ b/base/data/vectorcanvastest/clippingpath/00_pc_path.png diff --git a/base/data/vectorcanvastest/clippingpath/00_vc_path.png b/base/data/vectorcanvastest/clippingpath/00_vc_path.png Binary files differnew file mode 100644 index 0000000..78443af --- /dev/null +++ b/base/data/vectorcanvastest/clippingpath/00_vc_path.png diff --git a/base/data/vectorcanvastest/clippingrect/00_pc_rect.png b/base/data/vectorcanvastest/clippingrect/00_pc_rect.png Binary files differnew file mode 100644 index 0000000..9c365e1 --- /dev/null +++ b/base/data/vectorcanvastest/clippingrect/00_pc_rect.png diff --git a/base/data/vectorcanvastest/clippingrect/00_vc_rect.png b/base/data/vectorcanvastest/clippingrect/00_vc_rect.png Binary files differnew file mode 100644 index 0000000..9c365e1 --- /dev/null +++ b/base/data/vectorcanvastest/clippingrect/00_vc_rect.png diff --git a/base/data/vectorcanvastest/diagonallines/00_pc_nw-se.png b/base/data/vectorcanvastest/diagonallines/00_pc_nw-se.png Binary files differnew file mode 100644 index 0000000..5736c35 --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/00_pc_nw-se.png diff --git a/base/data/vectorcanvastest/diagonallines/00_vc_nw-se.png b/base/data/vectorcanvastest/diagonallines/00_vc_nw-se.png Binary files differnew file mode 100644 index 0000000..5736c35 --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/00_vc_nw-se.png diff --git a/base/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png b/base/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png Binary files differnew file mode 100644 index 0000000..bfffd8a --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png diff --git a/base/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png b/base/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png Binary files differnew file mode 100644 index 0000000..ae6b753 --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png diff --git a/base/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png b/base/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png Binary files differnew file mode 100644 index 0000000..75acdad --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png diff --git a/base/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png b/base/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png Binary files differnew file mode 100644 index 0000000..86a6799 --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png diff --git a/base/data/vectorcanvastest/diagonallines/03_pc_se-nw.png b/base/data/vectorcanvastest/diagonallines/03_pc_se-nw.png Binary files differnew file mode 100644 index 0000000..50502cc --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/03_pc_se-nw.png diff --git a/base/data/vectorcanvastest/diagonallines/03_vc_se-nw.png b/base/data/vectorcanvastest/diagonallines/03_vc_se-nw.png Binary files differnew file mode 100644 index 0000000..362f6e7 --- /dev/null +++ b/base/data/vectorcanvastest/diagonallines/03_vc_se-nw.png diff --git a/base/data/vectorcanvastest/lineorientation/00_pc_horizontal.png b/base/data/vectorcanvastest/lineorientation/00_pc_horizontal.png Binary files differnew file mode 100644 index 0000000..7bcd998 --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/00_pc_horizontal.png diff --git a/base/data/vectorcanvastest/lineorientation/00_vc_horizontal.png b/base/data/vectorcanvastest/lineorientation/00_vc_horizontal.png Binary files differnew file mode 100644 index 0000000..46c9b0a --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/00_vc_horizontal.png diff --git a/base/data/vectorcanvastest/lineorientation/01_pc_vertical.png b/base/data/vectorcanvastest/lineorientation/01_pc_vertical.png Binary files differnew file mode 100644 index 0000000..09f41db --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/01_pc_vertical.png diff --git a/base/data/vectorcanvastest/lineorientation/01_vc_vertical.png b/base/data/vectorcanvastest/lineorientation/01_vc_vertical.png Binary files differnew file mode 100644 index 0000000..7f5f1f7 --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/01_vc_vertical.png diff --git a/base/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png b/base/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png Binary files differnew file mode 100644 index 0000000..5966df6 --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png diff --git a/base/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png b/base/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png Binary files differnew file mode 100644 index 0000000..e43a844 --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png diff --git a/base/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png b/base/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png Binary files differnew file mode 100644 index 0000000..9ac4825 --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png diff --git a/base/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png b/base/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png Binary files differnew file mode 100644 index 0000000..d9e033a --- /dev/null +++ b/base/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png diff --git a/base/data/vectorcanvastest/matrix/00_pc_translate1.png b/base/data/vectorcanvastest/matrix/00_pc_translate1.png Binary files differnew file mode 100644 index 0000000..fe27cb3 --- /dev/null +++ b/base/data/vectorcanvastest/matrix/00_pc_translate1.png diff --git a/base/data/vectorcanvastest/matrix/00_vc_translate1.png b/base/data/vectorcanvastest/matrix/00_vc_translate1.png Binary files differnew file mode 100644 index 0000000..fe27cb3 --- /dev/null +++ b/base/data/vectorcanvastest/matrix/00_vc_translate1.png diff --git a/base/data/vectorcanvastest/matrix/01_pc_translate2.png b/base/data/vectorcanvastest/matrix/01_pc_translate2.png Binary files differnew file mode 100644 index 0000000..406bf57 --- /dev/null +++ b/base/data/vectorcanvastest/matrix/01_pc_translate2.png diff --git a/base/data/vectorcanvastest/matrix/01_vc_translate2.png b/base/data/vectorcanvastest/matrix/01_vc_translate2.png Binary files differnew file mode 100644 index 0000000..406bf57 --- /dev/null +++ b/base/data/vectorcanvastest/matrix/01_vc_translate2.png diff --git a/base/data/vectorcanvastest/matrix/02_pc_scale.png b/base/data/vectorcanvastest/matrix/02_pc_scale.png Binary files differnew file mode 100644 index 0000000..9e94fb0 --- /dev/null +++ b/base/data/vectorcanvastest/matrix/02_pc_scale.png diff --git a/base/data/vectorcanvastest/matrix/02_vc_scale.png b/base/data/vectorcanvastest/matrix/02_vc_scale.png Binary files differnew file mode 100644 index 0000000..fde62aa --- /dev/null +++ b/base/data/vectorcanvastest/matrix/02_vc_scale.png diff --git a/base/data/vectorcanvastest/matrix/03_pc_rotate.png b/base/data/vectorcanvastest/matrix/03_pc_rotate.png Binary files differnew file mode 100644 index 0000000..7a43a2a --- /dev/null +++ b/base/data/vectorcanvastest/matrix/03_pc_rotate.png diff --git a/base/data/vectorcanvastest/matrix/03_vc_rotate.png b/base/data/vectorcanvastest/matrix/03_vc_rotate.png Binary files differnew file mode 100644 index 0000000..7a22b7f --- /dev/null +++ b/base/data/vectorcanvastest/matrix/03_vc_rotate.png diff --git a/base/data/vectorcanvastest/patheffects/00_pc_dash_line.png b/base/data/vectorcanvastest/patheffects/00_pc_dash_line.png Binary files differnew file mode 100644 index 0000000..e08d3e2 --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/00_pc_dash_line.png diff --git a/base/data/vectorcanvastest/patheffects/00_vc_dash_line.png b/base/data/vectorcanvastest/patheffects/00_vc_dash_line.png Binary files differnew file mode 100644 index 0000000..e08d3e2 --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/00_vc_dash_line.png diff --git a/base/data/vectorcanvastest/patheffects/01_pc_dash_path.png b/base/data/vectorcanvastest/patheffects/01_pc_dash_path.png Binary files differnew file mode 100644 index 0000000..3a301354 --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/01_pc_dash_path.png diff --git a/base/data/vectorcanvastest/patheffects/01_vc_dash_path.png b/base/data/vectorcanvastest/patheffects/01_vc_dash_path.png Binary files differnew file mode 100644 index 0000000..7868b9a --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/01_vc_dash_path.png diff --git a/base/data/vectorcanvastest/patheffects/02_pc_dash_rect.png b/base/data/vectorcanvastest/patheffects/02_pc_dash_rect.png Binary files differnew file mode 100644 index 0000000..04f2ceb --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/02_pc_dash_rect.png diff --git a/base/data/vectorcanvastest/patheffects/02_vc_dash_rect.png b/base/data/vectorcanvastest/patheffects/02_vc_dash_rect.png Binary files differnew file mode 100644 index 0000000..5344eee --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/02_vc_dash_rect.png diff --git a/base/data/vectorcanvastest/patheffects/03_pc_circle.png b/base/data/vectorcanvastest/patheffects/03_pc_circle.png Binary files differnew file mode 100644 index 0000000..90fa28b --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/03_pc_circle.png diff --git a/base/data/vectorcanvastest/patheffects/03_vc_circle.png b/base/data/vectorcanvastest/patheffects/03_vc_circle.png Binary files differnew file mode 100644 index 0000000..8027297 --- /dev/null +++ b/base/data/vectorcanvastest/patheffects/03_vc_circle.png diff --git a/base/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png b/base/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png Binary files differnew file mode 100644 index 0000000..cefbf87 --- /dev/null +++ b/base/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png diff --git a/base/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png b/base/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png Binary files differnew file mode 100644 index 0000000..cefbf87 --- /dev/null +++ b/base/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png diff --git a/base/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png b/base/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png Binary files differnew file mode 100644 index 0000000..7bcd998 --- /dev/null +++ b/base/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png diff --git a/base/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png b/base/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png Binary files differnew file mode 100644 index 0000000..46c9b0a --- /dev/null +++ b/base/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png diff --git a/base/data/vectorcanvastest/uninitialized/00_pc_empty.png b/base/data/vectorcanvastest/uninitialized/00_pc_empty.png Binary files differnew file mode 100644 index 0000000..dec6694 --- /dev/null +++ b/base/data/vectorcanvastest/uninitialized/00_pc_empty.png diff --git a/base/data/vectorcanvastest/uninitialized/00_vc_empty.png b/base/data/vectorcanvastest/uninitialized/00_vc_empty.png Binary files differnew file mode 100644 index 0000000..9cbff6e --- /dev/null +++ b/base/data/vectorcanvastest/uninitialized/00_vc_empty.png diff --git a/base/debug_message.cc b/base/debug_message.cc new file mode 100644 index 0000000..e725dcf --- /dev/null +++ b/base/debug_message.cc @@ -0,0 +1,42 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +// Display the command line. This program is designed to be called from +// another process to display assertions. Since the other process has +// complete control of our command line, we assume that it did *not* +// add the program name as the first parameter. This allows us to just +// show the command line directly as the message. +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) { + LPWSTR cmdline = GetCommandLineW(); + MessageBox(NULL, cmdline, L"Kr\x00d8m", MB_TOPMOST); + return 0; +} diff --git a/base/debug_on_start.cc b/base/debug_on_start.cc new file mode 100644 index 0000000..7ae6cd1 --- /dev/null +++ b/base/debug_on_start.cc @@ -0,0 +1,89 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <windows.h> + +#include "base/debug_on_start.h" + +#include "base/base_switches.h" +#include "base/basictypes.h" +#include "base/debug_util.h" + +// 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 wchar_t* argument) +{ + 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. + DebugUtil::SpawnDebuggerOnProcess(GetCurrentProcessId()); + + // Wait for a debugger to come take us. + DebugUtil::WaitForDebugger(60, false); + } else if (FindArgument(GetCommandLine(), switches::kWaitForDebugger)) { + // Wait for a debugger to come take us. + DebugUtil::WaitForDebugger(60, true); + } + return 0; +} diff --git a/base/debug_on_start.h b/base/debug_on_start.h new file mode 100644 index 0000000..87afe9b --- /dev/null +++ b/base/debug_on_start.h @@ -0,0 +1,75 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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_ON_START_H__ +#define BASE_DEBUG_ON_START_H__ + +// This only works on Windows. +#ifdef _WIN32 + +#ifndef DECLSPEC_SELECTANY +#define DECLSPEC_SELECTANY __declspec(selectany) +#endif + +// 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 wchar_t* 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. + +// "Fix" the data section. +#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 data segment. +#pragma data_seg(pop) + +#endif // _WIN32 + +#endif // BASE_DEBUG_ON_START_H__ diff --git a/base/debug_util.cc b/base/debug_util.cc new file mode 100644 index 0000000..fb60f5a --- /dev/null +++ b/base/debug_util.cc @@ -0,0 +1,132 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/debug_util.h" + +namespace { + +// Minimalist key reader. +// Note: Does not use the CRT. +bool RegReadString(HKEY root, const wchar_t* subkey, + const wchar_t* value_name, wchar_t* buffer, int* len) { + HKEY key = NULL; + DWORD res = RegOpenKeyEx(root, subkey, 0, KEY_READ, &key); + if (ERROR_SUCCESS != res || key == NULL) + return false; + + DWORD type = 0; + DWORD buffer_size = *len * sizeof(wchar_t); + // We don't support REG_EXPAND_SZ. + res = RegQueryValueEx(key, value_name, NULL, &type, + reinterpret_cast<BYTE*>(buffer), &buffer_size); + if (ERROR_SUCCESS == res && buffer_size != 0 && type == REG_SZ) { + // Make sure the buffer is NULL terminated. + buffer[*len - 1] = 0; + *len = lstrlen(buffer); + RegCloseKey(key); + return true; + } + RegCloseKey(key); + return false; +} + +// Replaces each "%ld" in input per a value. Not efficient but it works. +// Note: Does not use the CRT. +bool StringReplace(const wchar_t* input, int value, wchar_t* output, + int output_len) { + memset(output, 0, output_len*sizeof(wchar_t)); + int input_len = lstrlen(input); + + for (int i = 0; i < input_len; ++i) { + int current_output_len = lstrlen(output); + + if (input[i] == L'%' && input[i + 1] == L'l' && input[i + 2] == L'd') { + // Make sure we have enough place left. + if ((current_output_len + 12) >= output_len) + return false; + + // Cheap _itow(). + wsprintf(output+current_output_len, L"%d", value); + i += 2; + } else { + if (current_output_len >= output_len) + return false; + output[current_output_len] = input[i]; + } + } + return true; +} + +} // namespace + +// Note: Does not use the CRT. +bool DebugUtil::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; +} + +// Note: Does not use the CRT. +bool DebugUtil::WaitForDebugger(int wait_seconds, bool silent) { + for (int i = 0; i < wait_seconds * 10; ++i) { + if (IsDebuggerPresent()) { + if (!silent) { + // If you hit here, you just use -debug-on-start or -debug-children. + __debugbreak(); + } + return true; + } + Sleep(100); + } + return false; +} diff --git a/base/debug_util.h b/base/debug_util.h new file mode 100644 index 0000000..54381e5 --- /dev/null +++ b/base/debug_util.h @@ -0,0 +1,44 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_DEBUG_UTIL_H__ +#define BASE_DEBUG_UTIL_H__ + +class DebugUtil { + public: + // Starts the registered system-wide JIT debugger to attach it to specified + // process. + static 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. + static bool WaitForDebugger(int wait_seconds, bool silent); +}; + +#endif // BASE_DEBUG_UTIL_H__ diff --git a/base/event_recorder.cc b/base/event_recorder.cc new file mode 100644 index 0000000..b4c89a3 --- /dev/null +++ b/base/event_recorder.cc @@ -0,0 +1,281 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/event_recorder.h" + +#include <mmsystem.h> + +#include "base/logging.h" +#include "base/time.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) { + CHECK(EventRecorder::current()); + return EventRecorder::current()->RecordWndProc(nCode, wParam, lParam); +} + +LRESULT CALLBACK StaticPlaybackWndProc(int nCode, WPARAM wParam, + LPARAM lParam) { + CHECK(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(std::wstring& filename) { + if (journal_hook_ != NULL) + return false; + if (is_recording_ || is_playing_) + return false; + + // Open the recording file. + DCHECK(file_ == NULL); + if (_wfopen_s(&file_, filename.c_str(), L"wb+") != 0) { + 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"; + fclose(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); + fclose(file_); + file_ = NULL; + + journal_hook_ = NULL; + is_recording_ = false; + } +} + +bool EventRecorder::StartPlayback(std::wstring& filename) { + if (journal_hook_ != NULL) + return false; + if (is_recording_ || is_playing_) + return false; + + // Open the recording file. + DCHECK(file_ == NULL); + if (_wfopen_s(&file_, filename.c_str(), L"rb") != 0) { + 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!"; + fclose(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); + fclose(file_); + file_ = NULL; + + ::timeEndPeriod(1); + + journal_hook_ = NULL; + is_playing_ = false; + } +} + +// Windows callback hook for the recorder. +LRESULT EventRecorder::RecordWndProc(int nCode, WPARAM wParam, LPARAM lParam) { + static bool recording_enabled = true; + EVENTMSG *msg_ptr = NULL; + + // The API says we have to do this. + // See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx + if (nCode < 0) + return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam); + + // Check for the break key being pressed and stop recording. + if (::GetKeyState(VK_CANCEL) & 0x8000) { + StopRecording(); + return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam); + } + + // The Journal Recorder must stop recording events when system modal + // dialogs are present. (see msdn link above) + switch(nCode) + { + case HC_SYSMODALON: + recording_enabled = false; + break; + case HC_SYSMODALOFF: + recording_enabled = true; + break; + } + + if (nCode == HC_ACTION && recording_enabled) { + // Aha - we have an event to record. + msg_ptr = reinterpret_cast<EVENTMSG*>(lParam); + msg_ptr->time = timeGetTime(); + fwrite(msg_ptr, sizeof(EVENTMSG), 1, file_); + fflush(file_); + } + + return CallNextHookEx(journal_hook_, nCode, wParam, lParam); +} + +// Windows callback for the playback mode. +LRESULT EventRecorder::PlaybackWndProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + static bool playback_enabled = true; + int delay = 0; + + switch(nCode) + { + // A system modal dialog box is being displayed. Stop playing back + // messages. + case HC_SYSMODALON: + playback_enabled = false; + break; + + // A system modal dialog box is destroyed. We can start playing back + // messages again. + case HC_SYSMODALOFF: + playback_enabled = true; + break; + + // Prepare to copy the next mouse or keyboard event to playback. + case HC_SKIP: + if (!playback_enabled) + break; + + // Read the next event from the record. + if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) + this->StopPlayback(); + break; + + // Copy the mouse or keyboard event to the EVENTMSG structure in lParam. + case HC_GETNEXT: + if (!playback_enabled) + break; + + memcpy(reinterpret_cast<void*>(lParam), &playback_msg_, sizeof(playback_msg_)); + + // The return value is the amount of time (in milliseconds) to wait + // before playing back the next message in the playback queue. Each + // time this is called, we recalculate the delay relative to our current + // wall clock. + delay = (playback_msg_.time - playback_first_msg_time_) - + (timeGetTime() - playback_start_time_); + if (delay < 0) + delay = 0; + return delay; + + // An application has called PeekMessage with wRemoveMsg set to PM_NOREMOVE + // indicating that the message is not removed from the message queue after + // PeekMessage processing. + case HC_NOREMOVE: + break; + } + + return CallNextHookEx(journal_hook_, nCode, wParam, lParam); +} + +} // namespace base
\ No newline at end of file diff --git a/base/event_recorder.h b/base/event_recorder.h new file mode 100644 index 0000000..71b815f --- /dev/null +++ b/base/event_recorder.h @@ -0,0 +1,115 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_EVENT_RECORDER_H_ +#define BASE_EVENT_RECORDER_H_ + +#include <string> +#include <windows.h> +#include "base/basictypes.h" + +namespace base { + +// A class for recording and playing back keyboard and mouse input events. +// +// Note - if you record events, and the playback with the windows in +// different sizes or positions, the playback will fail. When +// recording and playing, you should move the relevant windows +// to constant sizes and locations. +// TODO(mbelshe) For now this is a singleton. I believe that this class +// could be easily modified to: +// support two simultaneous recorders +// be playing back events while already recording events. +// Why? Imagine if the product had a "record a macro" feature. +// You might be recording globally, while recording or playing back +// a macro. I don't think two playbacks make sense. +class 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(std::wstring &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(std::wstring &filename); + + // Stops playback. + void StopPlayback(); + + // Is the EventRecorder currently playing. + bool is_playing() const { return is_playing_; } + + // 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); + + private: + // Create a new EventRecorder. Events are saved to the file filename. + // If the file already exists, it will be deleted before recording + // starts. + explicit EventRecorder() + : is_recording_(false), + is_playing_(false), + journal_hook_(NULL), + file_(NULL) { + } + ~EventRecorder(); + + static EventRecorder* current_; // Our singleton. + + bool is_recording_; + bool is_playing_; + HHOOK journal_hook_; + FILE* file_; + EVENTMSG playback_msg_; + int playback_first_msg_time_; + int playback_start_time_; + + DISALLOW_EVIL_CONSTRUCTORS(EventRecorder); +}; + +} // namespace base + +#endif // BASE_EVENT_RECORDER_H_ diff --git a/base/file_util.cc b/base/file_util.cc new file mode 100644 index 0000000..83cf534 --- /dev/null +++ b/base/file_util.cc @@ -0,0 +1,852 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/file_util.h" + +#include <windows.h> +#include <shellapi.h> +#include <shlobj.h> +#include <time.h> +#include <fstream> +#include <string> + +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "unicode/uniset.h" + +using std::wstring; + +namespace file_util { + +const wchar_t kPathSeparator = L'\\'; +const wchar_t kExtensionSeparator = L'.'; + +bool EndsWithSeparator(std::wstring* path) { + return (!path->empty() && (*path)[path->length() - 1] == kPathSeparator); +} + +void TrimTrailingSeparator(std::wstring* dir) { + while (EndsWithSeparator(dir)) + dir->resize(dir->length() - 1); +} + +void UpOneDirectory(std::wstring* dir) { + TrimTrailingSeparator(dir); + + wstring::size_type last_sep = dir->find_last_of(kPathSeparator); + if (last_sep != wstring::npos) + dir->resize(last_sep); +} + +void UpOneDirectoryOrEmpty(std::wstring* dir) { + TrimTrailingSeparator(dir); + + wstring::size_type last_sep = dir->find_last_of(kPathSeparator); + if (last_sep != wstring::npos) + dir->resize(last_sep); + else + dir->clear(); +} + +void TrimFilename(std::wstring* path) { + if (EndsWithSeparator(path)) { + TrimTrailingSeparator(path); + } else { + wstring::size_type last_sep = path->find_last_of(kPathSeparator); + if (last_sep != wstring::npos) + path->resize(last_sep); + } +} + +// TODO(mpcomplete): Make this platform-independent, etc. +wstring GetFilenameFromPath(const wstring& path) { + wstring::size_type pos = path.find_last_of(L"\\/"); + return wstring(path, pos == wstring::npos ? 0 : pos+1); +} + +wstring GetFileExtensionFromPath(const wstring& path) { + wstring file_name = GetFilenameFromPath(path); + wstring::size_type last_dot = file_name.rfind(L'.'); + return wstring(last_dot == wstring::npos? L"" : file_name, last_dot+1); +} + +void AppendToPath(std::wstring* path, const std::wstring& new_ending) { + if (!path) { + NOTREACHED(); + return; // Don't crash in this function in release builds. + } + + if (!EndsWithSeparator(path)) + path->push_back(kPathSeparator); + path->append(new_ending); +} + +void InsertBeforeExtension(std::wstring* path, const std::wstring& suffix) { + DCHECK(path); + + const wstring::size_type last_dot = path->rfind(kExtensionSeparator); + const wstring::size_type last_sep = path->rfind(kPathSeparator); + + if (last_dot == wstring::npos || + (last_sep != wstring::npos && last_dot < last_sep)) { + // The path looks something like "C:\pics.old\jojo" or "C:\pics\jojo". + // We should just append the suffix to the entire path. + path->append(suffix); + return; + } + + path->insert(last_dot, suffix); +} + +void ReplaceIllegalCharacters(std::wstring* file_name, int replace_char) { + DCHECK(file_name); + + // Control characters, formating 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. + + UErrorCode status = U_ZERO_ERROR; +#ifdef U_WCHAR_IS_UTF16 + UnicodeSet illegal_characters(UnicodeString( + L"[[\"*/:<>?\\\\|][:Cc:][:Cf:] - [\u200c\u200d]]"), status); +#else + UnicodeSet illegal_characters(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, check |ucs4 & 0xFFFEu == 0xFFFEu|, instead. + illegal_characters.add(0xFDD0, 0xFDEF); + for (int i = 0; i <= 0x10; ++i) { + int plane_base = 0x10000 * i; + illegal_characters.add(plane_base + 0xFFFE, plane_base + 0xFFFF); + } + illegal_characters.freeze(); + DCHECK(!illegal_characters.contains(replace_char) && replace_char < 0x10000); + + // Remove leading and trailing whitespace. + TrimWhitespace(*file_name, TRIM_ALL, file_name); + + std::wstring::size_type i = 0; + std::wstring::size_type length = file_name->size(); +#ifdef U_WCHAR_IS_UTF16 + // Using |span| method of UnicodeSet might speed things up a bit, but + // it's not likely to matter here. + const wchar_t* wstr = file_name->data(); + std::wstring temp; + temp.reserve(length); + while (i < length) { + UChar32 ucs4; + std::wstring::size_type prev = i; + U16_NEXT(wstr, i, length, ucs4); + if (illegal_characters.contains(ucs4)) { + temp.push_back(replace_char); + } else if (ucs4 < 0x10000) { + temp.push_back(ucs4); + } else { + temp.push_back(wstr[prev]); + temp.push_back(wstr[prev + 1]); + } + } + file_name->swap(temp); +#elif defined(U_WCHAR_IS_UTF32) + while (i < length) { + if (illegal_characters.contains(wstr[i])) { + *file_name[i] = replace_char; + } + } +#else +#error wchar_t* should be either UTF-16 or UTF-32 +#endif +} + +void ReplaceExtension(std::wstring* file_name, const std::wstring& extension) { + const wstring::size_type last_dot = file_name->rfind(L'.'); + wstring result = file_name->substr(0, last_dot); + if (!extension.empty() && extension != L".") { + if (extension.at(0) != L'.') + result.append(L"."); + result.append(extension); + } + file_name->swap(result); +} + +wstring GetDirectoryFromPath(const std::wstring& path) { + wchar_t path_buffer[MAX_PATH]; + wchar_t* file_ptr = NULL; + if (GetFullPathName(path.c_str(), MAX_PATH, path_buffer, &file_ptr) == 0) + return L""; + + wstring::size_type nc = file_ptr ? file_ptr - path_buffer : path.length(); + wstring directory(path, 0, nc); + TrimTrailingSeparator(&directory); + return directory; +} + +int CountFilesCreatedAfter(const std::wstring& path, + const FILETIME& comparison_time) { + int file_count = 0; + + WIN32_FIND_DATA find_file_data; + std::wstring filename_spec = path + L"\\*"; // All files in given dir + HANDLE find_handle = FindFirstFile(filename_spec.c_str(), &find_file_data); + if (find_handle != INVALID_HANDLE_VALUE) { + do { + // Don't count current or parent directories. + if ((wcscmp(find_file_data.cFileName, L"..") == 0) || + (wcscmp(find_file_data.cFileName, L".") == 0)) + continue; + + long result = CompareFileTime(&find_file_data.ftCreationTime, + &comparison_time); + // File was created after or on comparison time + if ((result == 1) || (result == 0)) + ++file_count; + } while (FindNextFile(find_handle, &find_file_data)); + FindClose(find_handle); + } + + return file_count; +} + +bool Delete(const std::wstring& path, bool recursive) { + if (path.length() >= MAX_PATH) + return false; + + // If we're not recursing use DeleteFile; it should be faster. DeleteFile + // fails if passed a directory though, which is why we fall through on + // failure to the SHFileOperation. + if (!recursive && DeleteFile(path.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 + wcscpy(double_terminated_path, path.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; + return (SHFileOperation(&file_operation) == 0); +} + +bool Move(const std::wstring& from_path, const std::wstring& to_path) { + // NOTE: I suspect we could support longer paths, but that would involve + // analyzing all our usage of files. + if (from_path.length() >= MAX_PATH || to_path.length() >= MAX_PATH) + return false; + return (MoveFileEx(from_path.c_str(), to_path.c_str(), + MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0); +} + +bool CopyFile(const std::wstring& from_path, const std::wstring& to_path) { + // NOTE: I suspect we could support longer paths, but that would involve + // analyzing all our usage of files. + if (from_path.length() >= MAX_PATH || to_path.length() >= MAX_PATH) + return false; + return (::CopyFile(from_path.c_str(), to_path.c_str(), false) != 0); +} + +bool ShellCopy(const std::wstring& from_path, const std::wstring& to_path, + bool recursive) { + // NOTE: I suspect we could support longer paths, but that would involve + // analyzing all our usage of files. + if (from_path.length() >= MAX_PATH || to_path.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, from_path.c_str()); +#pragma warning(suppress:4996) // don't complain about wcscpy deprecation + wcscpy(double_terminated_path_to, to_path.c_str()); + + SHFILEOPSTRUCT file_operation = {0}; + file_operation.wFunc = FO_COPY; + file_operation.pFrom = double_terminated_path_from; + file_operation.pTo = double_terminated_path_to; + file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION | + FOF_NOCONFIRMMKDIR; + if (!recursive) + file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY; + + return (SHFileOperation(&file_operation) == 0); +} + +bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path, + bool recursive) { + if (recursive) + return ShellCopy(from_path, to_path, true); + + // 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 (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA) + CreateDirectory(to_path); + else + ShellCopy(from_path, to_path, false); + } + + std::wstring directory(from_path); + AppendToPath(&directory, L"*.*"); + return ShellCopy(directory, to_path, false); +} + +bool PathExists(const std::wstring& path) { + return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES); +} + +bool PathIsWritable(const std::wstring& path) { + HANDLE dir = + CreateFile(path.c_str(), FILE_ADD_FILE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (dir == INVALID_HANDLE_VALUE) + return false; + + CloseHandle(dir); + return true; +} + +bool GetFileCreationLocalTimeFromHandle(HANDLE file_handle, + LPSYSTEMTIME creation_time) { + if (!file_handle) + return false; + + FILETIME utc_filetime; + if (!GetFileTime(file_handle, &utc_filetime, NULL, NULL)) + return false; + + FILETIME local_filetime; + if (!FileTimeToLocalFileTime(&utc_filetime, &local_filetime)) + return false; + + return !!FileTimeToSystemTime(&local_filetime, creation_time); +} + +bool GetFileCreationLocalTime(const std::wstring& filename, + LPSYSTEMTIME creation_time) { + ScopedHandle file_handle( + CreateFile(filename.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + return GetFileCreationLocalTimeFromHandle(file_handle.Get(), creation_time); +} + +bool ContentsEqual(const std::wstring& filename1, + const std::wstring& 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.c_str(), std::ios::in | std::ios::binary); + std::ifstream file2(filename2.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.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 ReadFileToString(const std::wstring& path, std::string* contents) { + FILE* file; + errno_t err = _wfopen_s(&file, path.c_str(), L"rbS"); + if (err != 0) + return false; + + char buf[1 << 16]; + size_t len; + while ((len = fread(buf, 1, sizeof(buf), file)) > 0) { + contents->append(buf, len); + } + fclose(file); + + return true; +} + +bool ResolveShortcut(std::wstring* path) { + HRESULT result; + IShellLink *shell = NULL; + bool is_resolved = false; + + // Get pointer to the IShellLink interface + result = CoCreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, IID_IShellLink, + reinterpret_cast<LPVOID*>(&shell)); + if (SUCCEEDED(result)) { + IPersistFile *persist = NULL; + // Query IShellLink for the IPersistFile interface + result = shell->QueryInterface(IID_IPersistFile, + reinterpret_cast<LPVOID*>(&persist)); + if (SUCCEEDED(result)) { + WCHAR temp_path[MAX_PATH]; + // Load the shell link + result = persist->Load(path->c_str(), STGM_READ); + if (SUCCEEDED(result)) { + // Try to find the target of a shortcut + result = shell->Resolve(0, SLR_NO_UI); + if (SUCCEEDED(result)) { + result = shell->GetPath(temp_path, MAX_PATH, + NULL, SLGP_UNCPRIORITY); + *path = temp_path; + is_resolved = true; + } + } + } + if (persist) + persist->Release(); + } + if (shell) + shell->Release(); + + return is_resolved; +} + +bool CreateShortcutLink(const wchar_t *source, const wchar_t *destination, + const wchar_t *working_dir, const wchar_t *arguments, + const wchar_t *description, const wchar_t *icon, + int icon_index) { + IShellLink *i_shell_link = NULL; + IPersistFile *i_persist_file = NULL; + + // Get pointer to the IShellLink interface + HRESULT result = CoCreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, IID_IShellLink, + reinterpret_cast<LPVOID*>(&i_shell_link)); + if (FAILED(result)) + return false; + + // Query IShellLink for the IPersistFile interface + result = i_shell_link->QueryInterface(IID_IPersistFile, + reinterpret_cast<LPVOID*>(&i_persist_file)); + if (FAILED(result)) { + i_shell_link->Release(); + return false; + } + + if (FAILED(i_shell_link->SetPath(source))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (working_dir && FAILED(i_shell_link->SetWorkingDirectory(working_dir))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (arguments && FAILED(i_shell_link->SetArguments(arguments))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (description && FAILED(i_shell_link->SetDescription(description))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (icon && FAILED(i_shell_link->SetIconLocation(icon, icon_index))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + result = i_persist_file->Save(destination, TRUE); + i_persist_file->Release(); + i_shell_link->Release(); + return SUCCEEDED(result); +} + + +bool UpdateShortcutLink(const wchar_t *source, const wchar_t *destination, + const wchar_t *working_dir, const wchar_t *arguments, + const wchar_t *description, const wchar_t *icon, + int icon_index) { + // Get pointer to the IPersistFile interface and load existing link + IShellLink *i_shell_link = NULL; + if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, IID_IShellLink, + reinterpret_cast<LPVOID*>(&i_shell_link)))) + return false; + + IPersistFile *i_persist_file = NULL; + if (FAILED(i_shell_link->QueryInterface( + IID_IPersistFile, reinterpret_cast<LPVOID*>(&i_persist_file)))) { + i_shell_link->Release(); + return false; + } + + if (FAILED(i_persist_file->Load(destination, 0))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (source && FAILED(i_shell_link->SetPath(source))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (working_dir && FAILED(i_shell_link->SetWorkingDirectory(working_dir))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (arguments && FAILED(i_shell_link->SetArguments(arguments))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (description && FAILED(i_shell_link->SetDescription(description))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + if (icon && FAILED(i_shell_link->SetIconLocation(icon, icon_index))) { + i_persist_file->Release(); + i_shell_link->Release(); + return false; + } + + HRESULT result = i_persist_file->Save(destination, TRUE); + i_persist_file->Release(); + i_shell_link->Release(); + return SUCCEEDED(result); +} + +bool GetTempDir(std::wstring* path) { + 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; + path->assign(temp_path); + TrimTrailingSeparator(path); + return true; +} + +bool CreateTemporaryFileName(std::wstring* temp_file) { + wchar_t temp_name[MAX_PATH + 1]; + std::wstring temp_path; + + if (!GetTempDir(&temp_path)) + return false; + + if (!GetTempFileName(temp_path.c_str(), L"", 0, temp_name)) + return false; // fail! + + DWORD path_len = GetLongPathName(temp_name, temp_name, MAX_PATH); + if (path_len > MAX_PATH + 1 || path_len == 0) + return false; // fail! + + temp_file->assign(temp_name, path_len); + return true; +} + +bool CreateNewTempDirectory(const std::wstring& prefix, + std::wstring* new_temp_path) { + std::wstring system_temp_dir; + if (!GetTempDir(&system_temp_dir)) + return false; + + std::wstring path_to_create; + srand(static_cast<uint32>(time(NULL))); + + int count = 0; + while (count < 50) { + // Try create a new temporary directory with random generated name. If + // the one exists, keep trying another path name until we reach some limit. + path_to_create.assign(system_temp_dir); + std::wstring new_dir_name; + new_dir_name.assign(prefix); + new_dir_name.append(IntToWString(rand() % kint16max)); + file_util::AppendToPath(&path_to_create, new_dir_name); + + if (::CreateDirectory(path_to_create.c_str(), NULL)) + break; + count++; + } + + if (count == 50) { + return false; + } + + new_temp_path->assign(path_to_create); + return true; +} + +bool CreateDirectory(const std::wstring& full_path) { + int err = SHCreateDirectoryEx(NULL, full_path.c_str(), NULL); + return err == ERROR_SUCCESS; +} + +bool GetFileSize(const std::wstring& file_path, int64* file_size) { + ScopedHandle file_handle( + CreateFile(file_path.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + + LARGE_INTEGER win32_file_size = {0}; + if (!GetFileSizeEx(file_handle, &win32_file_size)) { + return false; + } + + *file_size = win32_file_size.QuadPart; + return true; +} + +int ReadFile(const std::wstring& filename, char* data, int size) { + ScopedHandle file(CreateFile(filename.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL)); + if (file == INVALID_HANDLE_VALUE) + return -1; + + int ret_value; + DWORD read; + if (::ReadFile(file, data, size, &read, NULL) && read == size) { + ret_value = static_cast<int>(read); + } else { + ret_value = -1; + } + + return ret_value; +} + +int WriteFile(const std::wstring& filename, const char* data, int size) { + ScopedHandle file(CreateFile(filename.c_str(), + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + 0, + NULL)); + if (file == INVALID_HANDLE_VALUE) + return -1; + + int ret_value; + DWORD written; + if (::WriteFile(file, data, size, &written, NULL) && written == size) { + ret_value = static_cast<int>(written); + } else { + ret_value = -1; + } + + return ret_value; +} + +FileEnumerator::FileEnumerator(const std::wstring& root_path, + bool recursive, + FileEnumerator::FILE_TYPE file_type) + : recursive_(recursive), + file_type_(file_type), + is_in_find_op_(false), + find_handle_(INVALID_HANDLE_VALUE) { + pending_paths_.push(root_path); +} + +FileEnumerator::FileEnumerator(const std::wstring& root_path, + bool recursive, + FileEnumerator::FILE_TYPE file_type, + const std::wstring& pattern) + : recursive_(recursive), + file_type_(file_type), + is_in_find_op_(false), + pattern_(pattern), + find_handle_(INVALID_HANDLE_VALUE) { + pending_paths_.push(root_path); +} + +FileEnumerator::~FileEnumerator() { + if (find_handle_ != INVALID_HANDLE_VALUE) + FindClose(find_handle_); +} + +std::wstring FileEnumerator::Next() { + if (!is_in_find_op_) { + if (pending_paths_.empty()) + return std::wstring(); + + // The last find FindFirstFile operation is done, prepare a new one. + // root_path_ must have the trailing directory character. + root_path_ = pending_paths_.top(); + file_util::AppendToPath(&root_path_, std::wstring()); + pending_paths_.pop(); + + // Start a new find operation. + std::wstring src(root_path_); + + if (pattern_.empty()) + file_util::AppendToPath(&src, L"*"); // No pattern = match everything. + else + file_util::AppendToPath(&src, pattern_); + + find_handle_ = FindFirstFile(src.c_str(), &find_data_); + is_in_find_op_ = 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_) { + is_in_find_op_ = 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_.clear(); + + return Next(); + } + + std::wstring cur_file(find_data_.cFileName); + // Skip over . and .. + if (L"." == cur_file || L".." == cur_file) + return Next(); + + // Construct the absolute filename. + cur_file.insert(0, root_path_); + + if (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (recursive_) { + // If |cur_file| is a directory, and we are doing recursive searching, add + // it to pending_paths_ so we scan it after we finish scanning this + // directory. + pending_paths_.push(cur_file); + return (file_type_ & FileEnumerator::DIRECTORIES) ? cur_file : Next(); + } + + if ((file_type_ & FileEnumerator::DIRECTORIES) == 0) + return Next(); + } + return (file_type_ & FileEnumerator::FILES) ? cur_file : Next(); +} + +bool RenameFileAndResetSecurityDescriptor( + const std::wstring& source_file_path, + const std::wstring& target_file_path) { + // The MoveFile API does not reset the security descriptor on the target + // file. To ensure that the target file gets the correct security descriptor + // we create the target file initially in the target path, read its security + // descriptor and stamp this descriptor on the target file after the MoveFile + // API completes. + ScopedHandle temp_file_handle_for_security_desc( + CreateFileW(target_file_path.c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, + NULL)); + if (!temp_file_handle_for_security_desc.IsValid()) + return false; + + // Check how much we should allocate for the security descriptor. + unsigned long security_descriptor_size_in_bytes = 0; + GetFileSecurity(target_file_path.c_str(), DACL_SECURITY_INFORMATION, NULL, 0, + &security_descriptor_size_in_bytes); + if (ERROR_INSUFFICIENT_BUFFER != GetLastError() || + security_descriptor_size_in_bytes == 0) + return false; + + scoped_array<char> security_descriptor( + new char[security_descriptor_size_in_bytes]); + + if (!GetFileSecurity(target_file_path.c_str(), DACL_SECURITY_INFORMATION, + security_descriptor.get(), + security_descriptor_size_in_bytes, + &security_descriptor_size_in_bytes)) { + return false; + } + + temp_file_handle_for_security_desc.Set(INVALID_HANDLE_VALUE); + + if (!MoveFileEx(source_file_path.c_str(), target_file_path.c_str(), + MOVEFILE_COPY_ALLOWED)) { + return false; + } + + return !!SetFileSecurity(target_file_path.c_str(), + DACL_SECURITY_INFORMATION, + security_descriptor.get()); +} + +} // namespace diff --git a/base/file_util.h b/base/file_util.h new file mode 100644 index 0000000..9783e93 --- /dev/null +++ b/base/file_util.h @@ -0,0 +1,302 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file contains utility functions for dealing with the local +// filesystem. + +#ifndef BASE_FILE_UTIL_H__ +#define BASE_FILE_UTIL_H__ + +#include <windows.h> +#include <stack> +#include <string> + +#include "base/basictypes.h" + +namespace file_util { + +//----------------------------------------------------------------------------- +// Constants + +extern const wchar_t kPathSeparator; + + +//----------------------------------------------------------------------------- +// Functions that operate purely on a path string w/o touching the filesystem: + +// Returns true if the given path ends with a path separator character. +bool EndsWithSeparator(std::wstring* path); + +// Modifies a string by trimming all trailing separators from the end. +void TrimTrailingSeparator(std::wstring* dir); + +// Strips the topmost directory from the end of 'dir'. Assumes 'dir' does not +// refer to a file. +// If 'dir' is a root directory, return without change. +void UpOneDirectory(std::wstring* dir); + +// Strips the topmost directory from the end of 'dir'. Assumes 'dir' does not +// refer to a file. +// If 'dir' is a root directory, the result becomes empty string. +void UpOneDirectoryOrEmpty(std::wstring* dir); + +// Strips the filename component from the end of 'path'. +void TrimFilename(std::wstring* path); + +// Returns the filename portion of 'path', without any leading \'s or /'s. +std::wstring GetFilenameFromPath(const std::wstring& path); + +// Returns "jpg" for path "C:\pics\jojo.jpg", or an empty string if +// the file has no extension. +std::wstring GetFileExtensionFromPath(const std::wstring& path); + +// Returns the directory component of a path, without the trailing +// path separator, or an empty string on error. The function does not +// check for the existence of the path, so if it is passed a directory +// without the trailing \, it will interpret the last component of the +// path as a file and chomp it. This does not support relative paths. +// Examples: +// path == "C:\pics\jojo.jpg", returns "C:\pics" +// path == "C:\Windows\system32\", returns "C:\Windows\system32" +// path == "C:\Windows\system32", returns "C:\Windows" +std::wstring GetDirectoryFromPath(const std::wstring& path); + +// Appends new_ending to path, adding a separator between the two if necessary. +void AppendToPath(std::wstring* path, const std::wstring& new_ending); + +// Inserts |suffix| after the file name portion of |path| but before the +// extension. +// 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)" +void InsertBeforeExtension(std::wstring* path, const std::wstring& suffix); + +// 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. 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 '-'. +void ReplaceIllegalCharacters(std::wstring* file_name, int replace_char); + +// Replaces the extension of |file_name| with |extension|. If |file_name| +// does not have an extension, them |extension| is added. If |extention| is +// empty, then the extension is removed from |file_name|. +void ReplaceExtension(std::wstring* file_name, const std::wstring& extension); + +//----------------------------------------------------------------------------- +// Functions that involve filesystem access or modification: + +// Returns the number of files matching the current path that were +// created on or after the given FILETIME. Doesn't count ".." or ".". +// Filetime is UTC filetime, not LocalFiletime. +int CountFilesCreatedAfter(const std::wstring& path, + const FILETIME& file_time); + +// 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. +// +// WARNING: USING THIS WITH recursive==true IS EQUIVALENT +// TO "rm -rf", SO USE WITH CAUTION. +bool Delete(const std::wstring& path, bool recursive); + +// Moves the given path, whether it's a file or a directory. +// Returns true if successful, false otherwise. +bool Move(const std::wstring& from_path, const std::wstring& to_path); + +// Copies a single file. Use CopyDirectory to copy directories. +bool CopyFile(const std::wstring& from_path, const std::wstring& 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. +// Dont't use wildcards on the names, it may stop working without notice. +// +// If you only need to copy a file use CopyFile, it's faster. +bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path, + bool recursive); + +// Returns true if the given path exists on the local filesystem, +// false otherwise. +bool PathExists(const std::wstring& path); + +// Returns true if the given path is writable by the user, false otherwise. +bool PathIsWritable(const std::wstring& path); + +// Gets the creation time of the given file (expressed in the local timezone), +// and returns it via the creation_time parameter. Returns true if successful, +// false otherwise. +bool GetFileCreationLocalTime(const std::wstring& filename, + LPSYSTEMTIME creation_time); + +// Same as above, but takes a previously-opened file handle instead of a name. +bool GetFileCreationLocalTimeFromHandle(HANDLE file_handle, + LPSYSTEMTIME creation_time); + +// Returns true if the contents of the two files given are equal, false +// otherwise. If either file can't be read, returns false. +bool ContentsEqual(const std::wstring& filename1, + const std::wstring& filename2); + +// Read the file at |path| into |contents|, returning true on success. +// Useful for unit tests. +bool ReadFileToString(const std::wstring& path, std::string* contents); + +// Resolve Windows shortcut (.LNK file) +// Argument path specifies a valid LNK file. On success, return true and put +// the URL into path. If path is a invalid .LNK file, return false. +bool ResolveShortcut(std::wstring* path); + +// Create a Windows shortcut (.LNK file) +// This method creates a shortcut link using the information given. Ensure +// you have initialized COM before calling into this function. 'source' +// and 'destination' parameters are required, everything else can be NULL. +// 'source' is the existing file, 'destination' is the new link file to be +// created; for best resoults pass the filename with the .lnk extension. +// The 'icon' can specify a dll or exe in which case the icon index is the +// resource id. +// Note that if the shortcut exists it will overwrite it. +bool CreateShortcutLink(const wchar_t *source, const wchar_t *destination, + const wchar_t *working_dir, const wchar_t *arguments, + const wchar_t *description, const wchar_t *icon, + int icon_index); + +// Update a Windows shortcut (.LNK file). This method assumes the shortcut +// link already exists (otherwise false is returned). Ensure you have +// initialized COM before calling into this function. Only 'destination' +// parameter is required, everything else can be NULL (but if everthing else +// is NULL no changes are made to the shortcut). 'destination' is the link +// file to be updated. For best results pass the filename with the .lnk +// extension. +bool UpdateShortcutLink(const wchar_t *source, const wchar_t *destination, + const wchar_t *working_dir, const wchar_t *arguments, + const wchar_t *description, const wchar_t *icon, + int icon_index); + +// Get the temporary directory provided by the system. +bool GetTempDir(std::wstring* path); + +// Creates a temporary file name, but does it not create the file. It accesses +// the disk to do this, however. The full path is placed in 'temp_file', and the +// function returns true if was successful in creating the file name. +bool CreateTemporaryFileName(std::wstring* temp_file); + +// Create a new directory under TempPath. If prefix is provided, the new +// directory name is in the format of prefixyyyy. +// If success, return true and output the full path of the directory created. +bool CreateNewTempDirectory(const std::wstring& prefix, + std::wstring* new_temp_path); + +// Creates a directory, as well as creating any parent directories, if they +// don't exist. Returns 'true' on successful creation. +bool CreateDirectory(const std::wstring& full_path); + +// Returns the file size. Returns true on success. +bool GetFileSize(const std::wstring& file_path, int64* file_size); + +// Reads the given number of bytes from the file into the buffer. Returns +// the number of read bytes, or -1 on error. +int ReadFile(const std::wstring& 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. +int WriteFile(const std::wstring& filename, const char* data, int size); + +// A class for enumerating the files in a provided path. The order of the +// results is not guaranteed. +// +// DO NOT USE FROM THE MAIN THREAD of your application unless it is a test +// program where latency does not matter. This class is blocking. +class FileEnumerator { + public: + enum FILE_TYPE { + FILES = 0x1, + DIRECTORIES = 0x2, + FILES_AND_DIRECTORIES = 0x3 + }; + + // |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. + // + // The last parameter is an optional pattern for which files to match. This + // works like a Windows file pattern. For example, "*.txt" or "Foo???.doc". + // If unspecified, this will match all files. + FileEnumerator(const std::wstring& root_path, + bool recursive, + FileEnumerator::FILE_TYPE file_type); + FileEnumerator(const std::wstring& root_path, + bool recursive, + FileEnumerator::FILE_TYPE file_type, + const std::wstring& pattern); + ~FileEnumerator(); + + // Returns an empty string if there are no more results. + std::wstring Next(); + + private: + std::wstring root_path_; + bool recursive_; + FILE_TYPE file_type_; + std::wstring pattern_; // Empty when we want to find everything. + + // Set to true when there is a find operation open. This way, we can lazily + // start the operations when the caller calls Next(). + bool is_in_find_op_; + + // A stack that keeps track of which subdirectories we still need to + // enumerate in the breadth-first search. + std::stack<std::wstring> pending_paths_; + + WIN32_FIND_DATA find_data_; + HANDLE find_handle_; + + DISALLOW_EVIL_CONSTRUCTORS(FileEnumerator); +}; + +// Renames a file using the MoveFileEx API and ensures that the target file gets +// the correct security descriptor in the new path. +bool RenameFileAndResetSecurityDescriptor( + const std::wstring& source_file_path, + const std::wstring& target_file_path); + +} // namespace file_util + +#endif // BASE_FILE_UTIL_H__ diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc new file mode 100644 index 0000000..f82a894 --- /dev/null +++ b/base/file_util_unittest.cc @@ -0,0 +1,777 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <set> +#include <shellapi.h> +#include <shlobj.h> + +#include <fstream> +#include <iostream> + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class FileUtilTest : public testing::Test { + protected: + virtual void SetUp() { + // Name a subdirectory of the temp directory. + ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_dir_)); + file_util::AppendToPath(&test_dir_, L"FileUtilTest"); + + // Create a fresh, empty copy of this directory. + file_util::Delete(test_dir_, true); + CreateDirectory(test_dir_.c_str(), NULL); + } + virtual void TearDown() { + // Clean up test directory + ASSERT_TRUE(file_util::Delete(test_dir_, false)); + ASSERT_FALSE(file_util::PathExists(test_dir_)); + } + + // the path to temporary directory used to contain the test operations + std::wstring test_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: + FindResultCollector(file_util::FileEnumerator& enumerator) { + std::wstring cur_file; + while (!(cur_file = enumerator.Next()).empty()) { + // The file should not be returned twice. + EXPECT_TRUE(files_.end() == files_.find(cur_file)) + << "Same file returned twice"; + + // Save for later. + files_.insert(cur_file); + } + } + + // Returns true if the enumerator found the file. + bool HasFile(const std::wstring& file) const { + return files_.find(file) != files_.end(); + } + + private: + std::set<std::wstring> files_; +}; + +// Simple function to dump some text into a new file. +void CreateTextFile(const std::wstring& filename, + const std::wstring& contents) { + std::ofstream file; + file.open(filename.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 std::wstring& filename) { + WCHAR contents[64]; + std::wifstream file; + file.open(filename.c_str()); + EXPECT_TRUE(file.is_open()); + file.getline(contents, 64); + file.close(); + return std::wstring(contents); +} + +uint64 FileTimeAsUint64(const FILETIME& ft) { + ULARGE_INTEGER u; + u.LowPart = ft.dwLowDateTime; + u.HighPart = ft.dwHighDateTime; + return u.QuadPart; +} + +const struct append_case { + const wchar_t* path; + const wchar_t* ending; + const wchar_t* result; +} append_cases[] = { + {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"\\"}, +}; + +} // namespace + +TEST_F(FileUtilTest, AppendToPath) { + for (int i = 0; i < arraysize(append_cases); ++i) { + const append_case& value = append_cases[i]; + std::wstring result = value.path; + file_util::AppendToPath(&result, value.ending); + EXPECT_EQ(value.result, result); + } + +#ifdef NDEBUG + file_util::AppendToPath(NULL, L"path"); // asserts in debug mode +#endif +} + +static const struct InsertBeforeExtensionCase { + std::wstring path; + std::wstring suffix; + std::wstring result; +} kInsertBeforeExtension[] = { + {L"", L"", L""}, + {L"", L"txt", L"txt"}, + {L".", L"txt", L"txt."}, + {L".", L"", L"."}, + {L"foo.dll", L"txt", L"footxt.dll"}, + {L"foo.dll", L".txt", L"foo.txt.dll"}, + {L"foo", L"txt", L"footxt"}, + {L"foo", L".txt", L"foo.txt"}, + {L"foo.baz.dll", L"txt", L"foo.baztxt.dll"}, + {L"foo.baz.dll", L".txt", L"foo.baz.txt.dll"}, + {L"foo.dll", L"", L"foo.dll"}, + {L"foo.dll", L".", L"foo..dll"}, + {L"foo", L"", L"foo"}, + {L"foo", L".", L"foo."}, + {L"foo.baz.dll", L"", L"foo.baz.dll"}, + {L"foo.baz.dll", L".", L"foo.baz..dll"}, + {L"\\", L"", L"\\"}, + {L"\\", L"txt", L"\\txt"}, + {L"\\.", L"txt", L"\\txt."}, + {L"\\.", L"", L"\\."}, + {L"C:\\bar\\foo.dll", L"txt", L"C:\\bar\\footxt.dll"}, + {L"C:\\bar.baz\\foodll", L"txt", L"C:\\bar.baz\\foodlltxt"}, + {L"C:\\bar.baz\\foo.dll", L"txt", L"C:\\bar.baz\\footxt.dll"}, + {L"C:\\bar.baz\\foo.dll.exe", L"txt", L"C:\\bar.baz\\foo.dlltxt.exe"}, + {L"C:\\bar.baz\\foo", L"", L"C:\\bar.baz\\foo"}, + {L"C:\\bar.baz\\foo.exe", L"", L"C:\\bar.baz\\foo.exe"}, + {L"C:\\bar.baz\\foo.dll.exe", L"", L"C:\\bar.baz\\foo.dll.exe"}, + {L"C:\\bar\\baz\\foo.exe", L" (1)", L"C:\\bar\\baz\\foo (1).exe"}, +}; + +TEST_F(FileUtilTest, InsertBeforeExtensionTest) { + for (int i = 0; i < arraysize(kInsertBeforeExtension); ++i) { + std::wstring path(kInsertBeforeExtension[i].path); + file_util::InsertBeforeExtension(&path, kInsertBeforeExtension[i].suffix); + EXPECT_EQ(path, kInsertBeforeExtension[i].result); + } +} + +static const struct filename_case { + const wchar_t* path; + const wchar_t* filename; +} filename_cases[] = { + {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"}, +}; + +TEST_F(FileUtilTest, GetFilenameFromPath) { + for (int i = 0; i < arraysize(filename_cases); ++i) { + const filename_case& value = filename_cases[i]; + std::wstring result = file_util::GetFilenameFromPath(value.path); + EXPECT_EQ(value.filename, result); + } +} + +// Test finding the file type from a path name +static const struct extension_case { + const wchar_t* path; + const wchar_t* extension; +} extension_cases[] = { + {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"}, +}; + +TEST_F(FileUtilTest, GetFileExtensionFromPath) { + for (int i = 0; i < arraysize(extension_cases); ++i) { + const extension_case& ext = extension_cases[i]; + const std::wstring fext = file_util::GetFileExtensionFromPath(ext.path); + EXPECT_EQ(ext.extension, fext); + } +} + +// Test finding the directory component of a path +static const struct dir_case { + const wchar_t* full_path; + const wchar_t* directory; +} dir_cases[] = { + {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:"}, +}; + +TEST_F(FileUtilTest, GetDirectoryFromPath) { + for (int i = 0; i < arraysize(dir_cases); ++i) { + const dir_case& dir = dir_cases[i]; + const std::wstring parent = + file_util::GetDirectoryFromPath(dir.full_path); + EXPECT_EQ(dir.directory, parent); + } +} + +TEST_F(FileUtilTest, CountFilesCreatedAfter) { + // Create old file (that we don't want to count) + std::wstring old_file_name = test_dir_; + file_util::AppendToPath(&old_file_name, L"Old File.txt"); + CreateTextFile(old_file_name, L"Just call me Mr. Creakybits"); + + // Age to perfection + Sleep(100); + + // Establish our cutoff time + FILETIME test_start_time; + GetSystemTimeAsFileTime(&test_start_time); + EXPECT_EQ(0, file_util::CountFilesCreatedAfter(test_dir_, test_start_time)); + + // Create a new file (that we do want to count) + std::wstring new_file_name = test_dir_; + file_util::AppendToPath(&new_file_name, L"New File.txt"); + CreateTextFile(new_file_name, L"Waaaaaaaaaaaaaah."); + + // We should see only the new file. + EXPECT_EQ(1, file_util::CountFilesCreatedAfter(test_dir_, test_start_time)); + + // Delete new file, we should see no files after cutoff now + EXPECT_TRUE(file_util::Delete(new_file_name, false)); + EXPECT_EQ(0, file_util::CountFilesCreatedAfter(test_dir_, test_start_time)); +} + +// Tests that the Delete function works as expected, especially +// the recursion flag. Also coincidentally tests PathExists. +TEST_F(FileUtilTest, Delete) { + // Create a file + std::wstring file_name = test_dir_; + file_util::AppendToPath(&file_name, L"Test File.txt"); + CreateTextFile(file_name, L"I'm cannon fodder."); + + ASSERT_TRUE(file_util::PathExists(file_name)); + + std::wstring subdir_path = test_dir_; + file_util::AppendToPath(&subdir_path, L"Subdirectory"); + CreateDirectory(subdir_path.c_str(), NULL); + + ASSERT_TRUE(file_util::PathExists(subdir_path)); + + std::wstring directory_contents = test_dir_; + file_util::AppendToPath(&directory_contents, L"*"); + + // Delete non-recursively and check that only the file is deleted + ASSERT_TRUE(file_util::Delete(directory_contents, false)); + ASSERT_FALSE(file_util::PathExists(file_name)); + ASSERT_TRUE(file_util::PathExists(subdir_path)); + + // Delete recursively and make sure all contents are deleted + ASSERT_TRUE(file_util::Delete(directory_contents, true)); + ASSERT_FALSE(file_util::PathExists(file_name)); + ASSERT_FALSE(file_util::PathExists(subdir_path)); +} + +TEST_F(FileUtilTest, Move) { + // Create a directory + std::wstring dir_name_from(test_dir_); + file_util::AppendToPath(&dir_name_from, L"Move_From_Subdir"); + CreateDirectory(dir_name_from.c_str(), NULL); + ASSERT_TRUE(file_util::PathExists(dir_name_from)); + + // Create a file under the directory + std::wstring file_name_from(dir_name_from); + file_util::AppendToPath(&file_name_from, L"Move_Test_File.txt"); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::PathExists(file_name_from)); + + // Move the directory + std::wstring dir_name_to(test_dir_); + file_util::AppendToPath(&dir_name_to, L"Move_To_Subdir"); + std::wstring file_name_to(dir_name_to); + file_util::AppendToPath(&file_name_to, L"Move_Test_File.txt"); + + ASSERT_FALSE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::Move(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(file_util::PathExists(dir_name_from)); + EXPECT_FALSE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, CopyDirectoryRecursively) { + // Create a directory. + std::wstring dir_name_from(test_dir_); + file_util::AppendToPath(&dir_name_from, L"Copy_From_Subdir"); + CreateDirectory(dir_name_from.c_str(), NULL); + ASSERT_TRUE(file_util::PathExists(dir_name_from)); + + // Create a file under the directory. + std::wstring file_name_from(dir_name_from); + file_util::AppendToPath(&file_name_from, L"Copy_Test_File.txt"); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::PathExists(file_name_from)); + + // Create a subdirectory. + std::wstring subdir_name_from(dir_name_from); + file_util::AppendToPath(&subdir_name_from, L"Subdir"); + CreateDirectory(subdir_name_from.c_str(), NULL); + ASSERT_TRUE(file_util::PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + std::wstring file_name2_from(subdir_name_from); + file_util::AppendToPath(&file_name2_from, L"Copy_Test_File.txt"); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::PathExists(file_name2_from)); + + // Copy the directory recursively. + std::wstring dir_name_to(test_dir_); + file_util::AppendToPath(&dir_name_to, L"Copy_To_Subdir"); + std::wstring file_name_to(dir_name_to); + file_util::AppendToPath(&file_name_to, L"Copy_Test_File.txt"); + std::wstring subdir_name_to(dir_name_to); + file_util::AppendToPath(&subdir_name_to, L"Subdir"); + std::wstring file_name2_to(subdir_name_to); + file_util::AppendToPath(&file_name2_to, L"Copy_Test_File.txt"); + + ASSERT_FALSE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::CopyDirectory(dir_name_from, dir_name_to, true)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(dir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(subdir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name2_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_TRUE(file_util::PathExists(subdir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name2_to)); +} + +TEST_F(FileUtilTest, CopyDirectory) { + // Create a directory. + std::wstring dir_name_from(test_dir_); + file_util::AppendToPath(&dir_name_from, L"Copy_From_Subdir"); + CreateDirectory(dir_name_from.c_str(), NULL); + ASSERT_TRUE(file_util::PathExists(dir_name_from)); + + // Create a file under the directory. + std::wstring file_name_from(dir_name_from); + file_util::AppendToPath(&file_name_from, L"Copy_Test_File.txt"); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::PathExists(file_name_from)); + + // Create a subdirectory. + std::wstring subdir_name_from(dir_name_from); + file_util::AppendToPath(&subdir_name_from, L"Subdir"); + CreateDirectory(subdir_name_from.c_str(), NULL); + ASSERT_TRUE(file_util::PathExists(subdir_name_from)); + + // Create a file under the subdirectory. + std::wstring file_name2_from(subdir_name_from); + file_util::AppendToPath(&file_name2_from, L"Copy_Test_File.txt"); + CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::PathExists(file_name2_from)); + + // Copy the directory not recursively. + std::wstring dir_name_to(test_dir_); + file_util::AppendToPath(&dir_name_to, L"Copy_To_Subdir"); + std::wstring file_name_to(dir_name_to); + file_util::AppendToPath(&file_name_to, L"Copy_Test_File.txt"); + std::wstring subdir_name_to(dir_name_to); + file_util::AppendToPath(&subdir_name_to, L"Subdir"); + + ASSERT_FALSE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::CopyDirectory(dir_name_from, dir_name_to, false)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(dir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(subdir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name2_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_FALSE(file_util::PathExists(subdir_name_to)); +} + +TEST_F(FileUtilTest, CopyFile) { + // Create a directory + std::wstring dir_name_from(test_dir_); + file_util::AppendToPath(&dir_name_from, L"Copy_From_Subdir"); + CreateDirectory(dir_name_from.c_str(), NULL); + ASSERT_TRUE(file_util::PathExists(dir_name_from)); + + // Create a file under the directory + std::wstring file_name_from(dir_name_from); + file_util::AppendToPath(&file_name_from, L"Copy_Test_File.txt"); + const std::wstring file_contents(L"Gooooooooooooooooooooogle"); + CreateTextFile(file_name_from, file_contents); + ASSERT_TRUE(file_util::PathExists(file_name_from)); + + // Copy the file. + std::wstring dest_file(dir_name_from); + file_util::AppendToPath(&dest_file, L"DestFile.txt"); + ASSERT_TRUE(file_util::CopyFile(file_name_from, dest_file)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(dest_file)); + const std::wstring read_contents = ReadTextFile(dest_file); + EXPECT_EQ(file_contents, read_contents); +} + +TEST_F(FileUtilTest, GetFileCreationLocalTime) { + std::wstring file_name = test_dir_; + file_util::AppendToPath(&file_name, L"Test File.txt"); + + SYSTEMTIME start_time; + GetLocalTime(&start_time); + Sleep(100); + CreateTextFile(file_name, L"New file!"); + Sleep(100); + SYSTEMTIME end_time; + GetLocalTime(&end_time); + + SYSTEMTIME file_creation_time; + file_util::GetFileCreationLocalTime(file_name, &file_creation_time); + + FILETIME start_filetime; + SystemTimeToFileTime(&start_time, &start_filetime); + FILETIME end_filetime; + SystemTimeToFileTime(&end_time, &end_filetime); + FILETIME file_creation_filetime; + SystemTimeToFileTime(&file_creation_time, &file_creation_filetime); + + EXPECT_EQ(-1, CompareFileTime(&start_filetime, &file_creation_filetime)) << + "start time: " << FileTimeAsUint64(start_filetime) << ", " << + "creation time: " << FileTimeAsUint64(file_creation_filetime); + + EXPECT_EQ(-1, CompareFileTime(&file_creation_filetime, &end_filetime)) << + "creation time: " << FileTimeAsUint64(file_creation_filetime) << ", " << + "end time: " << FileTimeAsUint64(end_filetime); + + ASSERT_TRUE(DeleteFile(file_name.c_str())); +} + +typedef testing::Test ReadOnlyFileUtilTest; + +TEST(ReadOnlyFileUtilTest, ContentsEqual) { + std::wstring data_dir; + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + file_util::AppendToPath(&data_dir, L"base"); + file_util::AppendToPath(&data_dir, L"data"); + file_util::AppendToPath(&data_dir, L"file_util_unittest"); + ASSERT_TRUE(file_util::PathExists(data_dir)); + + std::wstring original_file = data_dir; + file_util::AppendToPath(&original_file, L"original.txt"); + std::wstring same_file = data_dir; + file_util::AppendToPath(&same_file, L"same.txt"); + std::wstring same_length_file = data_dir; + file_util::AppendToPath(&same_length_file, L"same_length.txt"); + std::wstring different_file = data_dir; + file_util::AppendToPath(&different_file, L"different.txt"); + std::wstring different_first_file = data_dir; + file_util::AppendToPath(&different_first_file, L"different_first.txt"); + std::wstring different_last_file = data_dir; + file_util::AppendToPath(&different_last_file, L"different_last.txt"); + std::wstring empty1_file = data_dir; + file_util::AppendToPath(&empty1_file, L"empty1.txt"); + std::wstring empty2_file = data_dir; + file_util::AppendToPath(&empty2_file, L"empty2.txt"); + std::wstring shortened_file = data_dir; + file_util::AppendToPath(&shortened_file, L"shortened.txt"); + std::wstring binary_file = data_dir; + file_util::AppendToPath(&binary_file, L"binary_file.bin"); + std::wstring binary_file_same = data_dir; + file_util::AppendToPath(&binary_file_same, L"binary_file_same.bin"); + std::wstring binary_file_diff = data_dir; + file_util::AppendToPath(&binary_file_diff, L"binary_file_diff.bin"); + + EXPECT_TRUE(file_util::ContentsEqual(original_file, original_file)); + EXPECT_TRUE(file_util::ContentsEqual(original_file, same_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, same_length_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, different_file)); + EXPECT_FALSE(file_util::ContentsEqual(L"bogusname", L"bogusname")); + EXPECT_FALSE(file_util::ContentsEqual(original_file, different_first_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, different_last_file)); + EXPECT_TRUE(file_util::ContentsEqual(empty1_file, empty2_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, shortened_file)); + EXPECT_FALSE(file_util::ContentsEqual(shortened_file, original_file)); + EXPECT_TRUE(file_util::ContentsEqual(binary_file, binary_file_same)); + EXPECT_FALSE(file_util::ContentsEqual(binary_file, binary_file_diff)); +} + +TEST_F(FileUtilTest, ResolveShortcutTest) { + std::wstring target_file = test_dir_; + file_util::AppendToPath(&target_file, L"Target.txt"); + CreateTextFile(target_file, L"This is the target."); + + std::wstring link_file = test_dir_; + file_util::AppendToPath(&link_file, L"Link.lnk"); + + HRESULT result; + IShellLink *shell = NULL; + IPersistFile *persist = NULL; + + CoInitialize(NULL); + // Temporarily create a shortcut for test + result = CoCreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, IID_IShellLink, + reinterpret_cast<LPVOID*>(&shell)); + EXPECT_TRUE(SUCCEEDED(result)); + result = shell->QueryInterface(IID_IPersistFile, + reinterpret_cast<LPVOID*>(&persist)); + EXPECT_TRUE(SUCCEEDED(result)); + result = shell->SetPath(target_file.c_str()); + EXPECT_TRUE(SUCCEEDED(result)); + result = shell->SetDescription(L"ResolveShortcutTest"); + EXPECT_TRUE(SUCCEEDED(result)); + result = persist->Save(link_file.c_str(), TRUE); + EXPECT_TRUE(SUCCEEDED(result)); + if (persist) + persist->Release(); + if (shell) + shell->Release(); + + bool is_solved; + is_solved = file_util::ResolveShortcut(&link_file); + EXPECT_TRUE(is_solved); + std::wstring contents; + contents = ReadTextFile(link_file); + EXPECT_EQ(L"This is the target.", contents); + + // Cleanning + DeleteFile(target_file.c_str()); + DeleteFile(link_file.c_str()); + CoUninitialize(); +} + +TEST_F(FileUtilTest, CreateShortcutTest) { + const wchar_t file_contents[] = L"This is another target."; + std::wstring target_file = test_dir_; + file_util::AppendToPath(&target_file, L"Target1.txt"); + CreateTextFile(target_file, file_contents); + + std::wstring link_file = test_dir_; + file_util::AppendToPath(&link_file, L"Link1.lnk"); + + CoInitialize(NULL); + EXPECT_TRUE(file_util::CreateShortcutLink(target_file.c_str(), + link_file.c_str(), + NULL, NULL, NULL, NULL, 0)); + std::wstring resolved_name = link_file; + EXPECT_TRUE(file_util::ResolveShortcut(&resolved_name)); + std::wstring read_contents = ReadTextFile(resolved_name); + EXPECT_EQ(file_contents, read_contents); + + DeleteFile(target_file.c_str()); + DeleteFile(link_file.c_str()); + CoUninitialize(); +} + +TEST_F(FileUtilTest, CreateTemporaryFileNameTest) { + std::wstring temp_file; + file_util::CreateTemporaryFileName(&temp_file); + EXPECT_EQ(file_util::PathExists(temp_file), true); +} + +TEST_F(FileUtilTest, CreateNewTempDirectoryTest) { + std::wstring temp_dir; + file_util::CreateNewTempDirectory(std::wstring(), &temp_dir); + EXPECT_EQ(file_util::PathExists(temp_dir), true); +} + +TEST_F(FileUtilTest, CreateDirectoryTest) { + std::wstring test_root = test_dir_; + file_util::AppendToPath(&test_root, L"create_directory_test"); + std::wstring test_path(test_root); + file_util::AppendToPath(&test_path, L"dir\\tree\\likely\\doesnt\\exist\\"); + + EXPECT_EQ(file_util::PathExists(test_path), false); + EXPECT_EQ(file_util::CreateDirectory(test_path), true); + EXPECT_EQ(file_util::PathExists(test_path), true); + EXPECT_EQ(file_util::Delete(test_root, true), true); + EXPECT_EQ(file_util::PathExists(test_root), false); + EXPECT_EQ(file_util::PathExists(test_path), false); +} + +static const struct { + std::wstring bad_name; + std::wstring good_name; +} kIllegalCharacterCases[] = { + {L"bad*file:name?.jpg", L"bad-file-name-.jpg"}, + {L"**********::::.txt", L"--------------.txt"}, + {L"bad*file\\name.jpg", L"bad-file-name.jpg"}, + // 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"}, + {L"\t bad*file\\name/.jpg ", L"bad-file-name-.jpg"}, + {L"bad\uFFFFfile\U0010FFFEname.jpg ", L"bad-file-name.jpg"}, + {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"}, +}; + +TEST_F(FileUtilTest, ReplaceIllegalCharactersTest) { + for (int i = 0; i < arraysize(kIllegalCharacterCases); ++i) { + std::wstring bad_name(kIllegalCharacterCases[i].bad_name); + file_util::ReplaceIllegalCharacters(&bad_name, L'-'); + EXPECT_EQ(kIllegalCharacterCases[i].good_name, bad_name); + } +} + +static const struct ReplaceExtensionCase { + std::wstring file_name; + std::wstring extension; + std::wstring result; +} kReplaceExtension[] = { + {L"", L"", L""}, + {L"", L"txt", L".txt"}, + {L".", L"txt", L".txt"}, + {L".", L"", L""}, + {L"foo.dll", L"txt", L"foo.txt"}, + {L"foo.dll", L".txt", L"foo.txt"}, + {L"foo", L"txt", L"foo.txt"}, + {L"foo", L".txt", L"foo.txt"}, + {L"foo.baz.dll", L"txt", L"foo.baz.txt"}, + {L"foo.baz.dll", L".txt", L"foo.baz.txt"}, + {L"foo.dll", L"", L"foo"}, + {L"foo.dll", L".", L"foo"}, + {L"foo", L"", L"foo"}, + {L"foo", L".", L"foo"}, + {L"foo.baz.dll", L"", L"foo.baz"}, + {L"foo.baz.dll", L".", L"foo.baz"}, +}; + +TEST_F(FileUtilTest, ReplaceExtensionTest) { + for (int i = 0; i < arraysize(kReplaceExtension); ++i) { + std::wstring file_name(kReplaceExtension[i].file_name); + file_util::ReplaceExtension(&file_name, kReplaceExtension[i].extension); + EXPECT_EQ(file_name, kReplaceExtension[i].result); + } +} + +TEST_F(FileUtilTest, FileEnumeratorTest) { + // Test an empty directory. + file_util::FileEnumerator f0(test_dir_, true, + file_util::FileEnumerator::FILES_AND_DIRECTORIES); + EXPECT_EQ(f0.Next(), L""); + EXPECT_EQ(f0.Next(), L""); + + // Populate the test dir. + file_util::CreateDirectory(test_dir_ + L"\\dir1"); + file_util::CreateDirectory(test_dir_ + L"\\dir2"); + CreateTextFile(test_dir_ + L"\\dir2\\dir2file.txt", L""); + file_util::CreateDirectory(test_dir_ + L"\\dir2\\inner"); + CreateTextFile(test_dir_ + L"\\dir2\\inner\\innerfile.txt", L""); + CreateTextFile(test_dir_ + L"\\file1.txt", L""); + CreateTextFile(test_dir_ + L"\\file2.txt", L""); + + // Only enumerate files. + file_util::FileEnumerator f1(test_dir_, true, + file_util::FileEnumerator::FILES); + FindResultCollector c1(f1); + EXPECT_TRUE(c1.HasFile(test_dir_ + L"\\file1.txt")); + EXPECT_TRUE(c1.HasFile(test_dir_ + L"\\file2.txt")); + EXPECT_TRUE(c1.HasFile(test_dir_ + L"\\dir2\\dir2file.txt")); + EXPECT_TRUE(c1.HasFile(test_dir_ + L"\\dir2\\inner\\innerfile.txt")); + + // Only enumerate directories. + file_util::FileEnumerator f2(test_dir_, true, + file_util::FileEnumerator::DIRECTORIES); + FindResultCollector c2(f2); + EXPECT_TRUE(c2.HasFile(test_dir_ + L"\\dir1")); + EXPECT_TRUE(c2.HasFile(test_dir_ + L"\\dir2")); + EXPECT_TRUE(c2.HasFile(test_dir_ + L"\\dir2\\inner")); + + // Enumerate files and directories. + file_util::FileEnumerator f3(test_dir_, true, + file_util::FileEnumerator::FILES_AND_DIRECTORIES); + FindResultCollector c3(f3); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\dir1")); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\dir2")); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\file1.txt")); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\file2.txt")); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\dir2\\dir2file.txt")); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\dir2\\dir2file.txt")); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\dir2\\inner")); + EXPECT_TRUE(c3.HasFile(test_dir_ + L"\\dir2\\inner\\innerfile.txt")); + + // Non-recursive operation. + file_util::FileEnumerator f4(test_dir_, false, + file_util::FileEnumerator::FILES_AND_DIRECTORIES); + FindResultCollector c4(f4); + EXPECT_TRUE(c4.HasFile(test_dir_ + L"\\dir1")); + EXPECT_TRUE(c4.HasFile(test_dir_ + L"\\dir2")); + EXPECT_TRUE(c4.HasFile(test_dir_ + L"\\file1.txt")); + EXPECT_TRUE(c4.HasFile(test_dir_ + L"\\file2.txt")); + + // Enumerate with a pattern. + file_util::FileEnumerator f5(test_dir_, true, + file_util::FileEnumerator::FILES_AND_DIRECTORIES, L"dir*"); + FindResultCollector c5(f5); + EXPECT_TRUE(c5.HasFile(test_dir_ + L"\\dir1")); + EXPECT_TRUE(c5.HasFile(test_dir_ + L"\\dir2")); + EXPECT_TRUE(c5.HasFile(test_dir_ + L"\\dir2\\dir2file.txt")); + EXPECT_TRUE(c5.HasFile(test_dir_ + L"\\dir2\\inner")); + EXPECT_TRUE(c5.HasFile(test_dir_ + L"\\dir2\\inner\\innerfile.txt")); + + // Make sure the destructor closes the find handle while in the middle of a + // query to allow TearDown to delete the directory. + file_util::FileEnumerator f6(test_dir_, true, + file_util::FileEnumerator::FILES_AND_DIRECTORIES); + EXPECT_FALSE(f6.Next().empty()); // Should have found something + // (we don't care what). +} diff --git a/base/file_version_info.cc b/base/file_version_info.cc new file mode 100644 index 0000000..90aedd2 --- /dev/null +++ b/base/file_version_info.cc @@ -0,0 +1,205 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/file_version_info.h" + +#include "base/logging.h" +#include "base/path_service.h" + +// This has to be last. +#include <strsafe.h> + +FileVersionInfo::FileVersionInfo(void* data, int language, int code_page) + : language_(language), code_page_(code_page) { + data_.reset((char*) data); + fixed_file_info_ = NULL; + UINT size; + ::VerQueryValue(data_.get(), L"\\", (LPVOID*)&fixed_file_info_, &size); +} + +FileVersionInfo::~FileVersionInfo() { + DCHECK(data_.get()); +} + +typedef struct { + WORD language; + WORD code_page; +} LanguageAndCodePage; + +// static +FileVersionInfo* FileVersionInfo::CreateFileVersionInfoForCurrentModule() { + std::wstring app_path; + if (!PathService::Get(base::FILE_MODULE, &app_path)) + return NULL; + + return CreateFileVersionInfo(app_path); +} + +// static +FileVersionInfo* FileVersionInfo::CreateFileVersionInfo( + const std::wstring& file_path) { + DWORD dummy; + const wchar_t* path = file_path.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 FileVersionInfo(data, translate->language, + translate->code_page); + + } else { + free(data); + return NULL; + } +} + +std::wstring FileVersionInfo::company_name() { + return GetStringValue(L"CompanyName"); +} + +std::wstring FileVersionInfo::company_short_name() { + return GetStringValue(L"CompanyShortName"); +} + +std::wstring FileVersionInfo::internal_name() { + return GetStringValue(L"InternalName"); +} + +std::wstring FileVersionInfo::product_name() { + return GetStringValue(L"ProductName"); +} + +std::wstring FileVersionInfo::product_short_name() { + return GetStringValue(L"ProductShortName"); +} + +std::wstring FileVersionInfo::comments() { + return GetStringValue(L"Comments"); +} + +std::wstring FileVersionInfo::legal_copyright() { + return GetStringValue(L"LegalCopyright"); +} + +std::wstring FileVersionInfo::product_version() { + return GetStringValue(L"ProductVersion"); +} + +std::wstring FileVersionInfo::file_description() { + return GetStringValue(L"FileDescription"); +} + +std::wstring FileVersionInfo::legal_trademarks() { + return GetStringValue(L"LegalTrademarks"); +} + +std::wstring FileVersionInfo::private_build() { + return GetStringValue(L"PrivateBuild"); +} + +std::wstring FileVersionInfo::file_version() { + return GetStringValue(L"FileVersion"); +} + +std::wstring FileVersionInfo::original_filename() { + return GetStringValue(L"OriginalFilename"); +} + +std::wstring FileVersionInfo::special_build() { + return GetStringValue(L"SpecialBuild"); +} + +std::wstring FileVersionInfo::last_change() { + return GetStringValue(L"LastChange"); +} + +bool FileVersionInfo::is_official_build() { + return (GetStringValue(L"Official Build").compare(L"1") == 0); +} + +bool FileVersionInfo::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\\%s", language, code_page, name); + LPVOID value = NULL; + uint32 size; + BOOL r = ::VerQueryValue(data_.get(), sub_block, &value, &size); + if (r && value) { + value_str->assign(static_cast<wchar_t*>(value)); + return true; + } + } + return false; +} + +std::wstring FileVersionInfo::GetStringValue(const wchar_t* name) { + std::wstring str; + if (GetValue(name, &str)) + return str; + else + return L""; +} + diff --git a/base/file_version_info.h b/base/file_version_info.h new file mode 100644 index 0000000..8d82e87 --- /dev/null +++ b/base/file_version_info.h @@ -0,0 +1,97 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_FILE_VERSION_INFO_H__ +#define BASE_FILE_VERSION_INFO_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" + +// Provides a way to access 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. + +class FileVersionInfo { + public: + // 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. + static FileVersionInfo* CreateFileVersionInfo(const std::wstring& file_path); + + // Creates a FileVersionInfo for the current application. Returns NULL in case + // of error. The returned object should be deleted when you are done with it. + static FileVersionInfo* + FileVersionInfo::CreateFileVersionInfoForCurrentModule(); + + ~FileVersionInfo(); + + // Accessors to the different version properties. + // Returns an empty string if the property is not found. + std::wstring company_name(); + std::wstring company_short_name(); + std::wstring product_name(); + std::wstring product_short_name(); + std::wstring internal_name(); + std::wstring product_version(); + std::wstring private_build(); + std::wstring special_build(); + std::wstring comments(); + std::wstring original_filename(); + std::wstring file_description(); + std::wstring file_version(); + std::wstring legal_copyright(); + std::wstring legal_trademarks(); + std::wstring last_change(); + bool is_official_build(); + + // Lets you access other properties not covered above. + bool GetValue(const wchar_t* name, std::wstring* value); + + // Similar to GetValue but returns a wstring (empty string if the property + // does not exist). + 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: + FileVersionInfo(void* data, int language, int code_page); + + scoped_ptr_malloc<char> data_; + int language_; + int code_page_; + // This is a pointer into the data_ if it exists. Otherwise NULL. + VS_FIXEDFILEINFO* fixed_file_info_; + + DISALLOW_EVIL_CONSTRUCTORS(FileVersionInfo); +}; + +#endif // BASE_FILE_VERSION_INFO_H__ diff --git a/base/file_version_info_unittest.cc b/base/file_version_info_unittest.cc new file mode 100644 index 0000000..272cba8f --- /dev/null +++ b/base/file_version_info_unittest.cc @@ -0,0 +1,153 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/scoped_ptr.h" +#include "base/file_version_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class FileVersionInfoTest : public testing::Test { +}; + +std::wstring GetTestDataPath() { + std::wstring path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + file_util::AppendToPath(&path, L"base"); + file_util::AppendToPath(&path, L"data"); + file_util::AppendToPath(&path, L"file_version_info_unittest"); + return path; +} + +} + +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) { + std::wstring dll_path = GetTestDataPath(); + file_util::AppendToPath(&dll_path, kDLLNames[i]); + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(dll_path)); + + int j = 0; + EXPECT_EQ(kExpectedValues[i][j++], version_info->company_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->company_short_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->product_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->product_short_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->internal_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->product_version()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->private_build()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->special_build()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->comments()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->original_filename()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->file_description()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->file_version()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->legal_copyright()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->legal_trademarks()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->last_change()); + } +} + +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) { + std::wstring dll_path = GetTestDataPath(); + file_util::AppendToPath(&dll_path, kDLLNames[i]); + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(dll_path)); + + EXPECT_EQ(kExpected[i], version_info->is_official_build()); + } +} + +TEST(FileVersionInfoTest, CustomProperties) { + std::wstring dll_path = GetTestDataPath(); + file_util::AppendToPath(&dll_path, L"FileVersionInfoTest1.dll"); + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(dll_path)); + + // Test few existing properties. + std::wstring str; + EXPECT_TRUE(version_info->GetValue(L"Custom prop 1", &str)); + EXPECT_EQ(L"Un", str); + EXPECT_EQ(L"Un", version_info->GetStringValue(L"Custom prop 1")); + + EXPECT_TRUE(version_info->GetValue(L"Custom prop 2", &str)); + EXPECT_EQ(L"Deux", str); + EXPECT_EQ(L"Deux", version_info->GetStringValue(L"Custom prop 2")); + + EXPECT_TRUE(version_info->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->GetStringValue(L"Custom prop 3")); + + // Test an non-existing property. + EXPECT_FALSE(version_info->GetValue(L"Unknown property", &str)); + EXPECT_EQ(L"", version_info->GetStringValue(L"Unknown property")); +} diff --git a/base/fix_wp64.h b/base/fix_wp64.h new file mode 100644 index 0000000..ff82a30 --- /dev/null +++ b/base/fix_wp64.h @@ -0,0 +1,100 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Various inline functions and macros to fix compilation of 32 bit target +// on MSVC with /Wp64 flag enabled. + +#ifndef BASE_FIX_WP64_H__ +#define BASE_FIX_WP64_H__ + +#include <windows.h> + +// Platform SDK fixes when building with /Wp64 for a 32 bits target. +#if !defined(_WIN64) && defined(_Wp64) + +#ifdef InterlockedExchangePointer +#undef InterlockedExchangePointer +// The problem is that the macro provided for InterlockedExchangePointer() is +// doing a (LONG) C-style cast that triggers invariably the warning C4312 when +// building on 32 bits. +inline void* InterlockedExchangePointer(void* volatile* target, void* value) { + return reinterpret_cast<void*>(static_cast<LONG_PTR>(InterlockedExchange( + reinterpret_cast<volatile LONG*>(target), + static_cast<LONG>(reinterpret_cast<LONG_PTR>(value))))); +} +#endif // #ifdef InterlockedExchangePointer + +#ifdef SetWindowLongPtrA +#undef SetWindowLongPtrA +// When build on 32 bits, SetWindowLongPtrX() is a macro that redirects to +// SetWindowLongX(). The problem is that this function takes a LONG argument +// instead of a LONG_PTR. +inline LONG_PTR SetWindowLongPtrA(HWND window, int index, LONG_PTR new_long) { + return ::SetWindowLongA(window, index, static_cast<LONG>(new_long)); +} +#endif // #ifdef SetWindowLongPtrA + +#ifdef SetWindowLongPtrW +#undef SetWindowLongPtrW +inline LONG_PTR SetWindowLongPtrW(HWND window, int index, LONG_PTR new_long) { + return ::SetWindowLongW(window, index, static_cast<LONG>(new_long)); +} +#endif // #ifdef SetWindowLongPtrW + +#ifdef GetWindowLongPtrA +#undef GetWindowLongPtrA +inline LONG_PTR GetWindowLongPtrA(HWND window, int index) { + return ::GetWindowLongA(window, index); +} +#endif // #ifdef GetWindowLongPtrA + +#ifdef GetWindowLongPtrW +#undef GetWindowLongPtrW +inline LONG_PTR GetWindowLongPtrW(HWND window, int index) { + return ::GetWindowLongW(window, index); +} +#endif // #ifdef GetWindowLongPtrW + +#ifdef SetClassLongPtrA +#undef SetClassLongPtrA +inline LONG_PTR SetClassLongPtrA(HWND window, int index, LONG_PTR new_long) { + return ::SetClassLongA(window, index, static_cast<LONG>(new_long)); +} +#endif // #ifdef SetClassLongPtrA + +#ifdef SetClassLongPtrW +#undef SetClassLongPtrW +inline LONG_PTR SetClassLongPtrW(HWND window, int index, LONG_PTR new_long) { + return ::SetClassLongW(window, index, static_cast<LONG>(new_long)); +} +#endif // #ifdef SetClassLongPtrW + +#endif // #if !defined(_WIN64) && defined(_Wp64) + +#endif // BASE_FIX_WP64_H__ diff --git a/base/fixed_string.h b/base/fixed_string.h new file mode 100644 index 0000000..857b057 --- /dev/null +++ b/base/fixed_string.h @@ -0,0 +1,96 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_FIXED_STRING_H__ +#define BASE_FIXED_STRING_H__ + +#include <string.h> + +#include "base/string_util.h" + +// This class manages a fixed-size, null-terminated string buffer. It is meant +// to be allocated on the stack, and it makes no use of the heap internally. In +// most cases you'll just want to use a std::(w)string, but when you need to +// avoid the heap, you can use this class instead. +// +// Methods are provided to read the null-terminated buffer and to append data +// to the buffer, and once the buffer fills-up, it simply discards any extra +// append calls. +// +// Since this object clips if the internal fixed buffer is exceeded, it is +// appropriate for exception handlers where the heap may be corrupted. Fixed +// buffers that overflow onto the heap are provided by Stack[W]String. +// (see stack_container.h). +template <class CharT, int MaxSize> +class FixedString { + public: + typedef CharTraits<CharT> char_traits; + + FixedString() : index_(0), truncated_(false) { + buf_[0] = CharT(0); + } + + // Returns true if the Append ever failed. + bool was_truncated() const { return truncated_; } + + // Returns the number of characters in the string, excluding the null + // terminator. + size_t size() const { return index_; } + + // Returns the null-terminated string. + const CharT* get() const { return buf_; } + CharT* get() { return buf_; } + + // Append an array of characters. The operation is bounds checked, and if + // there is insufficient room, then the was_truncated() flag is set to true. + void Append(const CharT* s, size_t n) { + if (char_traits::copy_num(buf_ + index_, arraysize(buf_) - index_, s, n)) { + index_ += n; + } else { + truncated_ = true; + } + } + + // Append a null-terminated string. + void Append(const CharT* s) { + Append(s, char_traits::length(s)); + } + + // Append a single character. + void Append(CharT c) { + Append(&c, 1); + } + + private: + CharT buf_[MaxSize]; + size_t index_; + bool truncated_; +}; + +#endif // BASE_FIXED_STRING_H__ diff --git a/base/fixed_string_unittest.cc b/base/fixed_string_unittest.cc new file mode 100644 index 0000000..a7a0767 --- /dev/null +++ b/base/fixed_string_unittest.cc @@ -0,0 +1,62 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/fixed_string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class FixedStringTest : public testing::Test { + }; +} + +TEST(FixedStringTest, TestBasic) { + const wchar_t kData[] = L"hello world"; + + FixedString<wchar_t, 40> buf; + + buf.Append(kData); + EXPECT_EQ(arraysize(kData)-1, buf.size()); + EXPECT_EQ(0, wcscmp(kData, buf.get())); + + buf.Append(' '); + buf.Append(kData); + const wchar_t kExpected[] = L"hello world hello world"; + EXPECT_EQ(arraysize(kExpected)-1, buf.size()); + EXPECT_EQ(0, wcscmp(kExpected, buf.get())); + EXPECT_EQ(false, buf.was_truncated()); +} + +// Disable this test in debug builds since overflow asserts. +TEST(FixedStringTest, TestOverflow) { + FixedString<wchar_t, 5> buf; + buf.Append(L"hello world"); + EXPECT_EQ(0, buf.size()); + EXPECT_EQ(0, buf.get()[0]); + EXPECT_EQ(true, buf.was_truncated()); +} diff --git a/base/gfx/SConscript b/base/gfx/SConscript new file mode 100644 index 0000000..4ec9e50 --- /dev/null +++ b/base/gfx/SConscript @@ -0,0 +1,83 @@ +# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('env')
+
+env = env.Clone()
+
+env.Prepend(
+ CPPPATH = [
+ '$SKIA_DIR/include',
+ '$SKIA_DIR/include/corecg',
+ '$SKIA_DIR/include/platform',
+ '$ZLIB_DIR',
+ '$LIBPNG_DIR',
+ '$ICU38_DIR/public/common',
+ '$ICU38_DIR/public/i18n',
+ '../..',
+ ],
+ CPPDEFINES = [
+ 'PNG_USER_CONFIG',
+ 'CHROME_PNG_WRITE_SUPPORT',
+ 'U_STATIC_IMPLEMENTATION',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ 'WIN32_LEAN_AND_MEAN',
+ ],
+ CCFLAGS = [
+ '/TP',
+
+ '/Wp64',
+ '/WX',
+
+ '/wd4503',
+ '/wd4819',
+ ],
+)
+
+input_files = [
+ 'bitmap_header.cc',
+ 'bitmap_platform_device.cc',
+ 'convolver.cc',
+ 'font_utils.cc',
+ 'image_operations.cc',
+ 'native_theme.cc',
+ 'platform_canvas.cc',
+ 'platform_device.cc',
+ 'png_decoder.cc',
+ 'png_encoder.cc',
+ 'point.cc',
+ 'rect.cc',
+ 'size.cc',
+ 'skia_utils.cc',
+ 'uniscribe.cc',
+ 'vector_canvas.cc',
+ 'vector_device.cc',
+]
+
+env.StaticLibrary('base_gfx', input_files)
diff --git a/base/gfx/bitmap_header.cc b/base/gfx/bitmap_header.cc new file mode 100644 index 0000000..3c54694 --- /dev/null +++ b/base/gfx/bitmap_header.cc @@ -0,0 +1,88 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/bitmap_header.h" + +namespace gfx { + +void CreateBitmapHeader(int width, int height, BITMAPINFOHEADER* hdr) { + CreateBitmapHeaderWithColorDepth(width, height, 32, hdr); +} + +void CreateBitmapHeaderWithColorDepth(int width, int height, int color_depth, + BITMAPINFOHEADER* hdr) { + // These values are shared with gfx::PlatformDevice + hdr->biSize = sizeof(BITMAPINFOHEADER); + hdr->biWidth = width; + hdr->biHeight = -height; // minus means top-down bitmap + hdr->biPlanes = 1; + hdr->biBitCount = color_depth; + hdr->biCompression = BI_RGB; // no compression + hdr->biSizeImage = 0; + hdr->biXPelsPerMeter = 1; + hdr->biYPelsPerMeter = 1; + hdr->biClrUsed = 0; + hdr->biClrImportant = 0; +} + + +void CreateBitmapV4Header(int width, int height, BITMAPV4HEADER* hdr) { + // Because bmp v4 header is just an extension, we just create a v3 header and + // copy the bits over to the v4 header. + BITMAPINFOHEADER header_v3; + CreateBitmapHeader(width, height, &header_v3); + memset(hdr, 0, sizeof(BITMAPV4HEADER)); + memcpy(hdr, &header_v3, sizeof(BITMAPINFOHEADER)); + + // Correct the size of the header and fill in the mask values. + hdr->bV4Size = sizeof(BITMAPV4HEADER); + hdr->bV4RedMask = 0x00ff0000; + hdr->bV4GreenMask = 0x0000ff00; + hdr->bV4BlueMask = 0x000000ff; + hdr->bV4AlphaMask = 0xff000000; +} + +// Creates a monochrome bitmap header. +void CreateMonochromeBitmapHeader(int width, + int height, + BITMAPINFOHEADER* hdr) { + hdr->biSize = sizeof(BITMAPINFOHEADER); + hdr->biWidth = width; + hdr->biHeight = -height; + hdr->biPlanes = 1; + hdr->biBitCount = 1; + hdr->biCompression = BI_RGB; + hdr->biSizeImage = 0; + hdr->biXPelsPerMeter = 1; + hdr->biYPelsPerMeter = 1; + hdr->biClrUsed = 0; + hdr->biClrImportant = 0; +} + +} // namespace gfx diff --git a/base/gfx/bitmap_header.h b/base/gfx/bitmap_header.h new file mode 100644 index 0000000..e8179c8 --- /dev/null +++ b/base/gfx/bitmap_header.h @@ -0,0 +1,56 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_BITMAP_HEADER_H__ +#define BASE_GFX_BITMAP_HEADER_H__ + +#include <windows.h> + +namespace gfx { + +// Creates a BITMAPINFOHEADER structure given the bitmap's size. +void CreateBitmapHeader(int width, int height, BITMAPINFOHEADER* hdr); + +// Creates a BITMAPINFOHEADER structure given the bitmap's size and +// color depth in bits per pixel. +void CreateBitmapHeaderWithColorDepth(int width, int height, int color_depth, + BITMAPINFOHEADER* hdr); + +// Creates a BITMAPV4HEADER structure given the bitmap's size. You probably +// only need to use BMP V4 if you need transparency (alpha channel). This +// function sets the AlphaMask to 0xff000000. +void CreateBitmapV4Header(int width, int height, BITMAPV4HEADER* hdr); + +// Creates a monochrome bitmap header. +void CreateMonochromeBitmapHeader(int width, int height, BITMAPINFOHEADER* hdr); + + +} // namespace gfx + +#endif // BASE_GFX_BITMAP_HEADER_H__ diff --git a/base/gfx/bitmap_platform_device.cc b/base/gfx/bitmap_platform_device.cc new file mode 100644 index 0000000..0a914e2 --- /dev/null +++ b/base/gfx/bitmap_platform_device.cc @@ -0,0 +1,482 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/bitmap_platform_device.h" + +#include "base/gfx/bitmap_header.h" +#include "base/logging.h" +#include "SkMatrix.h" +#include "SkRegion.h" +#include "SkUtils.h" + +namespace gfx { + +// When Windows draws text, is sets the fourth byte (which Skia uses for alpha) +// to zero. This means that if we try compositing with text that Windows has +// drawn, we get invalid color values (if the alpha is 0, the other channels +// should be 0 since Skia uses premultiplied colors) and strange results. +// +// HTML rendering only requires one bit of transparency. When you ask for a +// semitransparent div, the div itself is drawn in another layer as completely +// opaque, and then composited onto the lower layer with a transfer function. +// The only place an alpha channel is needed is to track what has been drawn +// and what has not been drawn. +// +// Therefore, when we allocate a new device, we fill it with this special +// color. Because Skia uses premultiplied colors, any color where the alpha +// channel is smaller than any component is impossible, so we know that no +// legitimate drawing will produce this color. We use 1 as the alpha value +// because 0 is produced when Windows draws text (even though it should be +// opaque). +// +// When a layer is done and we want to render it to a lower layer, we use +// fixupAlphaBeforeCompositing. This replaces all 0 alpha channels with +// opaque (to fix the text problem), and replaces this magic color value +// with transparency. The result is something that can be correctly +// composited. However, once this has been done, no more can be drawn to +// the layer because fixing the alphas *again* will result in incorrect +// values. +static const uint32_t kMagicTransparencyColor = 0x01FFFEFD; + +namespace { + +// Constrains position and size to fit within available_size. If |size| is -1, +// all the available_size is used. Returns false if the position is out of +// available_size. +bool Constrain(int available_size, int* position, int *size) { + if (*size < -2) + return false; + + if (*position < 0) { + if (*size != -1) + *size += *position; + *position = 0; + } + if (*size == 0 || *position >= available_size) + return false; + + if (*size > 0) { + int overflow = (*position + *size) - available_size; + if (overflow > 0) { + *size -= overflow; + } + } else { + // Fill up available size. + *size = available_size - *position; + } + return true; +} + +// If the pixel value is 0, it gets set to kMagicTransparencyColor. +void PrepareAlphaForGDI(uint32_t* pixel) { + if (*pixel == 0) { + *pixel = kMagicTransparencyColor; + } +} + +// If the pixel value is kMagicTransparencyColor, it gets set to 0. Otherwise +// if the alpha is 0, the alpha is set to 255. +void PostProcessAlphaForGDI(uint32_t* pixel) { + if (*pixel == kMagicTransparencyColor) { + *pixel = 0; + } else if ((*pixel & 0xFF000000) == 0) { + *pixel |= 0xFF000000; + } +} + +// Sets the opacity of the specified value to 0xFF. +void MakeOpaqueAlphaAdjuster(uint32_t* pixel) { + *pixel |= 0xFF000000; +} + +// See the declaration of kMagicTransparencyColor at the top of the file. +void FixupAlphaBeforeCompositing(uint32_t* pixel) { + if (*pixel == kMagicTransparencyColor) + *pixel = 0; + else + *pixel |= 0xFF000000; +} + +} // namespace + +class BitmapPlatformDevice::BitmapPlatformDeviceData + : public base::RefCounted<BitmapPlatformDeviceData> { + public: + explicit BitmapPlatformDeviceData(HBITMAP hbitmap); + + // Create/destroy hdc_, which is the memory DC for our bitmap data. + HDC GetBitmapDC(); + void ReleaseBitmapDC(); + bool IsBitmapDCCreated() const; + + // Sets the transform and clip operations. This will not update the DC, + // but will mark the config as dirty. The next call of LoadConfig will + // pick up these changes. + void SetMatrixClip(const SkMatrix& transform, const SkRegion& region); + // The device offset is already modified according to the transformation. + void SetDeviceOffset(int x, int y); + + const SkMatrix& transform() const { + return transform_; + } + + protected: + // Loads the current transform (taking into account offset_*_) and clip + // into the DC. Can be called even when the DC is NULL (will be a NOP). + void LoadConfig(); + + // Windows bitmap corresponding to our surface. + HBITMAP hbitmap_; + + // Lazily-created DC used to draw into the bitmap, see getBitmapDC. + HDC hdc_; + + // Additional offset applied to the transform. See setDeviceOffset(). + int offset_x_; + int offset_y_; + + // True when there is a transform or clip that has not been set to the DC. + // The DC is retrieved for every text operation, and the transform and clip + // do not change as much. We can save time by not loading the clip and + // transform for every one. + bool config_dirty_; + + // Translation assigned to the DC: we need to keep track of this separately + // so it can be updated even if the DC isn't created yet. + SkMatrix transform_; + + // The current clipping + SkRegion clip_region_; + + private: + friend class base::RefCounted<BitmapPlatformDeviceData>; + ~BitmapPlatformDeviceData(); + + DISALLOW_EVIL_CONSTRUCTORS(BitmapPlatformDeviceData); +}; + +BitmapPlatformDevice::BitmapPlatformDeviceData::BitmapPlatformDeviceData( + HBITMAP hbitmap) + : hbitmap_(hbitmap), + hdc_(NULL), + offset_x_(0), + offset_y_(0), + config_dirty_(true) { // Want to load the config next time. + // Initialize the clip region to the entire bitmap. + BITMAP bitmap_data; + if (GetObject(hbitmap_, sizeof(BITMAP), &bitmap_data)) { + SkIRect rect; + rect.set(0, 0, bitmap_data.bmWidth, bitmap_data.bmHeight); + clip_region_ = SkRegion(rect); + } + + transform_.reset(); +} + +BitmapPlatformDevice::BitmapPlatformDeviceData::~BitmapPlatformDeviceData() { + if (hdc_) + ReleaseBitmapDC(); + + // this will free the bitmap data as well as the bitmap handle + DeleteObject(hbitmap_); +} + +HDC BitmapPlatformDevice::BitmapPlatformDeviceData::GetBitmapDC() { + if (!hdc_) { + hdc_ = CreateCompatibleDC(NULL); + InitializeDC(hdc_); + HGDIOBJ old_bitmap = SelectObject(hdc_, hbitmap_); + // When the memory DC is created, its display surface is exactly one + // monochrome pixel wide and one monochrome pixel high. Since we select our + // own bitmap, we must delete the previous one. + DeleteObject(old_bitmap); + } + + LoadConfig(); + return hdc_; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::ReleaseBitmapDC() { + DCHECK(hdc_); + DeleteDC(hdc_); + hdc_ = NULL; +} + +bool BitmapPlatformDevice::BitmapPlatformDeviceData::IsBitmapDCCreated() const { + return hdc_ != NULL; +} + + +void BitmapPlatformDevice::BitmapPlatformDeviceData::SetMatrixClip( + const SkMatrix& transform, + const SkRegion& region) { + transform_ = transform; + clip_region_ = region; + config_dirty_ = true; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::SetDeviceOffset(int x, + int y) { + offset_x_ = x; + offset_y_ = y; + config_dirty_ = true; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::LoadConfig() { + if (!config_dirty_ || !hdc_) + return; // Nothing to do. + config_dirty_ = false; + + // Transform. + SkMatrix t(transform_); + t.postTranslate(SkIntToScalar(-offset_x_), SkIntToScalar(-offset_y_)); + LoadTransformToDC(hdc_, t); + // We don't use transform_ for the clipping region since the translation is + // already applied to offset_x_ and offset_y_. + t.reset(); + t.postTranslate(SkIntToScalar(-offset_x_), SkIntToScalar(-offset_y_)); + LoadClippingRegionToDC(hdc_, clip_region_, t); +} + +// We use this static factory function instead of the regular constructor so +// that we can create the pixel data before calling the constructor. This is +// required so that we can call the base class' constructor with the pixel +// data. +BitmapPlatformDevice* BitmapPlatformDevice::create(HDC screen_dc, + int width, + int height, + bool is_opaque, + HANDLE shared_section) { + SkBitmap bitmap; + + // CreateDIBSection appears to get unhappy if we create an empty bitmap, so + // we just expand it here. + if (width == 0) + width = 1; + if (height == 0) + height = 1; + + BITMAPINFOHEADER hdr; + CreateBitmapHeader(width, height, &hdr); + + void* data; + HBITMAP hbitmap = CreateDIBSection(screen_dc, + reinterpret_cast<BITMAPINFO*>(&hdr), 0, + &data, + shared_section, 0); + + // If we run out of GDI objects or some other error occurs, we won't get a + // bitmap here. This will cause us to crash later because the data pointer is + // NULL. To make sure that we can assign blame for those crashes to this code, + // we deliberately crash here, even in release mode. + CHECK(hbitmap); + + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap.setPixels(data); + bitmap.setIsOpaque(is_opaque); + + if (is_opaque) { +#ifndef NDEBUG + // To aid in finding bugs, we set the background color to something + // obviously wrong so it will be noticable when it is not cleared + bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green +#endif + } else { + // A transparent layer is requested: fill with our magic "transparent" + // color, see the declaration of kMagicTransparencyColor above + sk_memset32(static_cast<uint32_t*>(data), kMagicTransparencyColor, + width * height); + } + + // The device object will take ownership of the HBITMAP. + return new BitmapPlatformDevice(new BitmapPlatformDeviceData(hbitmap), bitmap); +} + +// The device will own the HBITMAP, which corresponds to also owning the pixel +// data. Therefore, we do not transfer ownership to the SkDevice's bitmap. +BitmapPlatformDevice::BitmapPlatformDevice(BitmapPlatformDeviceData* data, + const SkBitmap& bitmap) + : PlatformDevice(bitmap), + data_(data) { +} + +// The copy constructor just adds another reference to the underlying data. +// We use a const cast since the default Skia definitions don't define the +// proper constedness that we expect (accessBitmap should really be const). +BitmapPlatformDevice::BitmapPlatformDevice(const BitmapPlatformDevice& other) + : PlatformDevice( + const_cast<BitmapPlatformDevice&>(other).accessBitmap(true)), + data_(other.data_) { +} + +BitmapPlatformDevice::~BitmapPlatformDevice() { +} + +BitmapPlatformDevice& BitmapPlatformDevice::operator=( + const BitmapPlatformDevice& other) { + data_ = other.data_; + return *this; +} + +HDC BitmapPlatformDevice::getBitmapDC() { + return data_->GetBitmapDC(); +} + +void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform, + const SkRegion& region) { + data_->SetMatrixClip(transform, region); +} + +void BitmapPlatformDevice::setDeviceOffset(int x, int y) { + data_->SetDeviceOffset(x, y); +} + +void BitmapPlatformDevice::drawToHDC(HDC dc, int x, int y, + const RECT* src_rect) { + bool created_dc = !data_->IsBitmapDCCreated(); + HDC source_dc = getBitmapDC(); + + RECT temp_rect; + if (!src_rect) { + temp_rect.left = 0; + temp_rect.right = width(); + temp_rect.top = 0; + temp_rect.bottom = height(); + src_rect = &temp_rect; + } + + int copy_width = src_rect->right - src_rect->left; + int copy_height = src_rect->bottom - src_rect->top; + + // We need to reset the translation for our bitmap or (0,0) won't be in the + // upper left anymore + SkMatrix identity; + identity.reset(); + + LoadTransformToDC(source_dc, identity); + if (isOpaque()) { + BitBlt(dc, + x, + y, + copy_width, + copy_height, + source_dc, + src_rect->left, + src_rect->top, + SRCCOPY); + } else { + BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + AlphaBlend(dc, + x, + y, + copy_width, + copy_height, + source_dc, + src_rect->left, + src_rect->top, + copy_width, + copy_height, + blend_function); + } + LoadTransformToDC(source_dc, data_->transform()); + + if (created_dc) + data_->ReleaseBitmapDC(); +} + +void BitmapPlatformDevice::prepareForGDI(int x, int y, int width, int height) { + processPixels<PrepareAlphaForGDI>(x, y, width, height); +} + +void BitmapPlatformDevice::postProcessGDI(int x, int y, int width, int height) { + processPixels<PostProcessAlphaForGDI>(x, y, width, height); +} + +void BitmapPlatformDevice::makeOpaque(int x, int y, int width, int height) { + processPixels<MakeOpaqueAlphaAdjuster>(x, y, width, height); +} + +void BitmapPlatformDevice::fixupAlphaBeforeCompositing() { + const SkBitmap& bitmap = accessBitmap(true); + SkAutoLockPixels lock(bitmap); + uint32_t* data = bitmap.getAddr32(0, 0); + + size_t words = bitmap.rowBytes() / sizeof(uint32_t) * bitmap.height(); + for (size_t i = 0; i < words; i++) { + if (data[i] == kMagicTransparencyColor) + data[i] = 0; + else + data[i] |= 0xFF000000; + } +} + +// Returns the color value at the specified location. +SkColor BitmapPlatformDevice::getColorAt(int x, int y) { + const SkBitmap& bitmap = accessBitmap(false); + SkAutoLockPixels lock(bitmap); + uint32_t* data = bitmap.getAddr32(0, 0); + return static_cast<SkColor>(data[x + y * width()]); +} + +void BitmapPlatformDevice::onAccessBitmap(SkBitmap* bitmap) { + // FIXME(brettw) OPTIMIZATION: We should only flush if we know a GDI + // operation has occurred on our DC. + if (data_->IsBitmapDCCreated()) + GdiFlush(); +} + +template<BitmapPlatformDevice::adjustAlpha adjustor> +void BitmapPlatformDevice::processPixels(int x, + int y, + int width, + int height) { + const SkBitmap& bitmap = accessBitmap(true); + DCHECK_EQ(bitmap.config(), SkBitmap::kARGB_8888_Config); + const SkMatrix& matrix = data_->transform(); + int bitmap_start_x = SkScalarRound(matrix.getTranslateX()) + x; + int bitmap_start_y = SkScalarRound(matrix.getTranslateY()) + y; + + if (Constrain(bitmap.width(), &bitmap_start_x, &width) && + Constrain(bitmap.height(), &bitmap_start_y, &height)) { + SkAutoLockPixels lock(bitmap); + DCHECK_EQ(bitmap.rowBytes() % sizeof(uint32_t), 0u); + size_t row_words = bitmap.rowBytes() / sizeof(uint32_t); + // Set data to the first pixel to be modified. + uint32_t* data = bitmap.getAddr32(0, 0) + (bitmap_start_y * row_words) + + bitmap_start_x; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + adjustor(data + j); + } + data += row_words; + } + } +} + +} // namespace gfx diff --git a/base/gfx/bitmap_platform_device.h b/base/gfx/bitmap_platform_device.h new file mode 100644 index 0000000..481067a --- /dev/null +++ b/base/gfx/bitmap_platform_device.h @@ -0,0 +1,135 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_BITMAP_PLATFORM_DEVICE_H__ +#define BASE_GFX_BITMAP_PLATFORM_DEVICE_H__ + +#include "base/gfx/platform_device.h" +#include "base/ref_counted.h" + +namespace gfx { + +// A device is basically a wrapper around SkBitmap that provides a surface for +// SkCanvas to draw into. Our device provides a surface Windows can also write +// to. BitmapPlatformDevice creates a bitmap using CreateDIBSection() in a +// format that Skia supports and can then use this to draw ClearType into, etc. +// This pixel data is provided to the bitmap that the device contains so that it +// can be shared. +// +// The device owns the pixel data, when the device goes away, the pixel data +// also becomes invalid. THIS IS DIFFERENT THAN NORMAL SKIA which uses +// reference counting for the pixel data. In normal Skia, you could assign +// another bitmap to this device's bitmap and everything will work properly. +// For us, that other bitmap will become invalid as soon as the device becomes +// invalid, which may lead to subtle bugs. Therefore, DO NOT ASSIGN THE +// DEVICE'S PIXEL DATA TO ANOTHER BITMAP, make sure you copy instead. +class BitmapPlatformDevice : public PlatformDevice { + public: + // Factory function. The screen DC is used to create the bitmap, and will not + // be stored beyond this function. is_opaque should be set if the caller + // knows the bitmap will be completely opaque and allows some optimizations. + // + // The shared_section parameter is optional (pass NULL for default behavior). + // If shared_section is non-null, then it must be a handle to a file-mapping + // object returned by CreateFileMapping. See CreateDIBSection for details. + static BitmapPlatformDevice* create(HDC screen_dc, + int width, + int height, + bool is_opaque, + HANDLE shared_section); + + // Copy constructor. When copied, devices duplicate their internal data, so + // stay linked. This is because their implementation is very heavyweight + // (lots of memory and some GDI objects). If a device has been copied, both + // clip rects and other state will stay in sync. + // + // This means it will NOT work to duplicate a device and assign it to a + // canvas, because the two canvases will each set their own clip rects, and + // the resulting GDI clip rect will be random. + // + // Copy constucting and "=" is designed for saving the device or passing it + // around to another routine willing to deal with the bitmap data directly. + BitmapPlatformDevice(const BitmapPlatformDevice& other); + virtual ~BitmapPlatformDevice(); + + // See warning for copy constructor above. + BitmapPlatformDevice& operator=(const BitmapPlatformDevice& other); + + // Retrieves the bitmap DC, which is the memory DC for our bitmap data. The + // bitmap DC is lazy created. + virtual HDC getBitmapDC(); + virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region); + virtual void setDeviceOffset(int x, int y); + + virtual void drawToHDC(HDC dc, int x, int y, const RECT* src_rect); + virtual void prepareForGDI(int x, int y, int width, int height); + virtual void postProcessGDI(int x, int y, int width, int height); + virtual void makeOpaque(int x, int y, int width, int height); + virtual void fixupAlphaBeforeCompositing(); + virtual bool IsVectorial() { return false; } + + // Returns the color value at the specified location. This does not + // consider any transforms that may be set on the device. + SkColor getColorAt(int x, int y); + + protected: + // Flushes the Windows device context so that the pixel data can be accessed + // directly by Skia. Overridden from SkDevice, this is called when Skia + // starts accessing pixel data. + virtual void onAccessBitmap(SkBitmap* bitmap); + + private: + // Function pointer used by the processPixels method for setting the alpha + // value of a particular pixel. + typedef void (*adjustAlpha)(uint32_t* pixel); + + // Reference counted data that can be shared between multiple devices. This + // allows copy constructors and operator= for devices to work properly. The + // bitmaps used by the base device class are already refcounted and copyable. + class BitmapPlatformDeviceData; + + // Private constructor. + BitmapPlatformDevice(BitmapPlatformDeviceData* data, const SkBitmap& bitmap); + + // Loops through each of the pixels in the specified range, invoking + // adjustor for the alpha value of each pixel. If |width| or |height| are -1, + // the available width/height is used. + template<adjustAlpha adjustor> + void processPixels(int x, + int y, + int width, + int height); + + // Data associated with this device, guaranteed non-null. + scoped_refptr<BitmapPlatformDeviceData> data_; +}; + +} // namespace gfx + +#endif // BASE_GFX_BITMAP_PLATFORM_DEVICE_H__ diff --git a/base/gfx/convolver.cc b/base/gfx/convolver.cc new file mode 100644 index 0000000..e56493e --- /dev/null +++ b/base/gfx/convolver.cc @@ -0,0 +1,359 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/gfx/convolver.h" +#include "base/logging.h" + +namespace gfx { + +namespace { + +// Converts the argument to an 8-bit unsigned value by clamping to the range +// 0-255. +inline uint8 ClampTo8(int32 a) { + if (static_cast<uint32>(a) < 256) + return a; // Avoid the extra check in the common case. + if (a < 0) + return 0; + return 255; +} + +// Stores a list of rows in a circular buffer. The usage is you write into it +// by calling AdvanceRow. It will keep track of which row in the buffer it +// should use next, and the total number of rows added. +class CircularRowBuffer { + public: + // The number of pixels in each row is given in |source_row_pixel_width|. + // The maximum number of rows needed in the buffer is |max_y_filter_size| + // (we only need to store enough rows for the biggest filter). + // + // We use the |first_input_row| to compute the coordinates of all of the + // following rows returned by Advance(). + CircularRowBuffer(int dest_row_pixel_width, int max_y_filter_size, + int first_input_row) + : row_byte_width_(dest_row_pixel_width * 4), + num_rows_(max_y_filter_size), + next_row_(0), + next_row_coordinate_(first_input_row) { + buffer_.resize(row_byte_width_ * max_y_filter_size); + row_addresses_.resize(num_rows_); + } + + // Moves to the next row in the buffer, returning a pointer to the beginning + // of it. + uint8* AdvanceRow() { + uint8* row = &buffer_[next_row_ * row_byte_width_]; + next_row_coordinate_++; + + // Set the pointer to the next row to use, wrapping around if necessary. + next_row_++; + if (next_row_ == num_rows_) + next_row_ = 0; + return row; + } + + // Returns a pointer to an "unrolled" array of rows. These rows will start + // at the y coordinate placed into |*first_row_index| and will continue in + // order for the maximum number of rows in this circular buffer. + // + // The |first_row_index_| may be negative. This means the circular buffer + // starts before the top of the image (it hasn't been filled yet). + uint8* const* GetRowAddresses(int* first_row_index) { + // Example for a 4-element circular buffer holding coords 6-9. + // Row 0 Coord 8 + // Row 1 Coord 9 + // Row 2 Coord 6 <- next_row_ = 2, next_row_coordinate_ = 10. + // Row 3 Coord 7 + // + // The "next" row is also the first (lowest) coordinate. This computation + // may yield a negative value, but that's OK, the math will work out + // since the user of this buffer will compute the offset relative + // to the first_row_index and the negative rows will never be used. + *first_row_index = next_row_coordinate_ - num_rows_; + + int cur_row = next_row_; + for (int i = 0; i < num_rows_; i++) { + row_addresses_[i] = &buffer_[cur_row * row_byte_width_]; + + // Advance to the next row, wrapping if nexessary. + cur_row++; + if (cur_row == num_rows_) + cur_row = 0; + } + return &row_addresses_[0]; + } + + private: + // The buffer storing the rows. They are packed, each one row_byte_width_. + std::vector<uint8> buffer_; + + // Number of bytes per row in the |buffer_|. + int row_byte_width_; + + // The number of rows available in the buffer. + int num_rows_; + + // The next row index we should write into. This wraps around as the + // circular buffer is used. + int next_row_; + + // The y coordinate of the |next_row_|. This is incremented each time a + // new row is appended and does not wrap. + int next_row_coordinate_; + + // Buffer used by GetRowAddresses(). + std::vector<uint8*> row_addresses_; +}; + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the num_values() of the filter. +template<bool has_alpha> +void ConvolveHorizontally(const uint8* src_data, + const ConvolusionFilter1D& filter, + unsigned char* out_row) { + // Loop over each pixel on this row in the output image. + int num_values = filter.num_values(); + for (int out_x = 0; out_x < num_values; out_x++) { + // Get the filter that determines the current output pixel. + int filter_offset, filter_length; + const int16* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filter_length| pixels (4 bytes each) after this. + const uint8* row_to_filter = &src_data[filter_offset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + int32 accum[4] = {0}; + for (int filter_x = 0; filter_x < filter_length; filter_x++) { + int16 cur_filter = filter_values[filter_x]; + accum[0] += cur_filter * row_to_filter[filter_x * 4 + 0]; + accum[1] += cur_filter * row_to_filter[filter_x * 4 + 1]; + accum[2] += cur_filter * row_to_filter[filter_x * 4 + 2]; + if (has_alpha) + accum[3] += cur_filter * row_to_filter[filter_x * 4 + 3]; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + accum[0] >>= ConvolusionFilter1D::kShiftBits; + accum[1] >>= ConvolusionFilter1D::kShiftBits; + accum[2] >>= ConvolusionFilter1D::kShiftBits; + if (has_alpha) + accum[3] >>= ConvolusionFilter1D::kShiftBits; + + // Store the new pixel. + out_row[out_x * 4 + 0] = ClampTo8(accum[0]); + out_row[out_x * 4 + 1] = ClampTo8(accum[1]); + out_row[out_x * 4 + 2] = ClampTo8(accum[2]); + if (has_alpha) + out_row[out_x * 4 + 3] = ClampTo8(accum[3]); + } +} + +// Does vertical convolusion to produce one output row. The filter values and +// length are given in the first two parameters. These are applied to each +// of the rows pointed to in the |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +template<bool has_alpha> +void ConvolveVertically(const int16* filter_values, + int filter_length, + uint8* const* source_data_rows, + int pixel_width, + uint8* out_row) { + // We go through each column in the output and do a vertical convolusion, + // generating one output pixel each time. + for (int out_x = 0; out_x < pixel_width; out_x++) { + // Compute the number of bytes over in each row that the current column + // we're convolving starts at. The pixel will cover the next 4 bytes. + int byte_offset = out_x * 4; + + // Apply the filter to one column of pixels. + int32 accum[4] = {0}; + for (int filter_y = 0; filter_y < filter_length; filter_y++) { + int16 cur_filter = filter_values[filter_y]; + accum[0] += cur_filter * source_data_rows[filter_y][byte_offset + 0]; + accum[1] += cur_filter * source_data_rows[filter_y][byte_offset + 1]; + accum[2] += cur_filter * source_data_rows[filter_y][byte_offset + 2]; + if (has_alpha) + accum[3] += cur_filter * source_data_rows[filter_y][byte_offset + 3]; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of precision. + accum[0] >>= ConvolusionFilter1D::kShiftBits; + accum[1] >>= ConvolusionFilter1D::kShiftBits; + accum[2] >>= ConvolusionFilter1D::kShiftBits; + if (has_alpha) + accum[3] >>= ConvolusionFilter1D::kShiftBits; + + // Store the new pixel. + out_row[byte_offset + 0] = ClampTo8(accum[0]); + out_row[byte_offset + 1] = ClampTo8(accum[1]); + out_row[byte_offset + 2] = ClampTo8(accum[2]); + if (has_alpha) { + uint8 alpha = ClampTo8(accum[3]); + + // Make sure the alpha channel doesn't come out larger than any of the + // color channels. We use premultipled alpha channels, so this should + // never happen, but rounding errors will cause this from time to time. + // These "impossible" colors will cause overflows (and hence random pixel + // values) when the resulting bitmap is drawn to the screen. + // + // We only need to do this when generating the final output row (here). + int max_color_channel = std::max(out_row[byte_offset + 0], + std::max(out_row[byte_offset + 1], out_row[byte_offset + 2])); + if (alpha < max_color_channel) + out_row[byte_offset + 3] = max_color_channel; + else + out_row[byte_offset + 3] = alpha; + } else { + // No alpha channel, the image is opqaue. + out_row[byte_offset + 3] = 0xff; + } + } +} + +} // namespace + +// ConvolusionFilter1D --------------------------------------------------------- + +void ConvolusionFilter1D::AddFilter(int filter_offset, + const float* filter_values, + int filter_length) { + FilterInstance instance; + instance.data_location = static_cast<int>(filter_values_.size()); + instance.offset = filter_offset; + instance.length = filter_length; + filters_.push_back(instance); + + DCHECK(filter_length > 0); + for (int i = 0; i < filter_length; i++) + filter_values_.push_back(FloatToFixed(filter_values[i])); + + max_filter_ = std::max(max_filter_, filter_length); +} + +void ConvolusionFilter1D::AddFilter(int filter_offset, + const int16* filter_values, + int filter_length) { + FilterInstance instance; + instance.data_location = static_cast<int>(filter_values_.size()); + instance.offset = filter_offset; + instance.length = filter_length; + filters_.push_back(instance); + + DCHECK(filter_length > 0); + for (int i = 0; i < filter_length; i++) + filter_values_.push_back(filter_values[i]); + + max_filter_ = std::max(max_filter_, filter_length); +} + +// BGRAConvolve2D ------------------------------------------------------------- + +void BGRAConvolve2D(const uint8* source_data, + int source_byte_row_stride, + bool source_has_alpha, + const ConvolusionFilter1D& filter_x, + const ConvolusionFilter1D& filter_y, + uint8* output) { + int max_y_filter_size = filter_y.max_filter(); + + // The next row in the input that we will generate a horizontally + // convolved row for. If the filter doesn't start at the beginning of the + // image (this is the case when we are only resizing a subset), then we + // don't want to generate any output rows before that. Compute the starting + // row for convolusion as the first pixel for the first vertical filter. + int filter_offset, filter_length; + const int16* filter_values = + filter_y.FilterForValue(0, &filter_offset, &filter_length); + int next_x_row = filter_offset; + + // We loop over each row in the input doing a horizontal convolusion. This + // will result in a horizontally convolved image. We write the results into + // a circular buffer of convolved rows and do vertical convolusion as rows + // are available. This prevents us from having to store the entire + // intermediate image and helps cache coherency. + CircularRowBuffer row_buffer(filter_x.num_values(), max_y_filter_size, + filter_offset); + + // Loop over every possible output row, processing just enough horizontal + // convolusions to run each subsequent vertical convolusion. + int output_row_byte_width = filter_x.num_values() * 4; + int num_output_rows = filter_y.num_values(); + for (int out_y = 0; out_y < num_output_rows; out_y++) { + filter_values = filter_y.FilterForValue(out_y, + &filter_offset, &filter_length); + + // Generate output rows until we have enough to run the current filter. + while (next_x_row < filter_offset + filter_length) { + if (source_has_alpha) { + ConvolveHorizontally<true>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } else { + ConvolveHorizontally<false>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } + next_x_row++; + } + + // Compute where in the output image this row of final data will go. + uint8* cur_output_row = &output[out_y * output_row_byte_width]; + + // Get the list of rows that the circular buffer has, in order. + int first_row_in_circular_buffer; + uint8* const* rows_to_convolve = + row_buffer.GetRowAddresses(&first_row_in_circular_buffer); + + // Now compute the start of the subset of those rows that the filter + // needs. + uint8* const* first_row_for_filter = + &rows_to_convolve[filter_offset - first_row_in_circular_buffer]; + + if (source_has_alpha) { + ConvolveVertically<true>(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row); + } else { + ConvolveVertically<false>(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row); + } + } +} + +} // namespace gfx
\ No newline at end of file diff --git a/base/gfx/convolver.h b/base/gfx/convolver.h new file mode 100644 index 0000000..8a2310e --- /dev/null +++ b/base/gfx/convolver.h @@ -0,0 +1,156 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_CONVOLVER_H__ +#define BASE_GFX_CONVOLVER_H__ + +#include <vector> + +#include "base/basictypes.h" + +namespace gfx { + +// Represents a filter in one dimension. Each output pixel has one entry in this +// object for the filter values contributing to it. You build up the filter +// list by calling AddFilter for each output pixel (in order). +// +// We do 2-dimensional convolusion by first convolving each row by one +// ConvolusionFilter1D, then convolving each column by another one. +// +// Entries are stored in fixed point, shifted left by kShiftBits. +class ConvolusionFilter1D { + public: + // The number of bits that fixed point values are shifted by. + enum { kShiftBits = 14 }; + + ConvolusionFilter1D() : max_filter_(0) { + } + + // Convert between floating point and our fixed point representation. + static inline int16 FloatToFixed(float f) { + return static_cast<int16>(f * (1 << kShiftBits)); + } + static inline unsigned char FixedToChar(int16 x) { + return static_cast<unsigned char>(x >> kShiftBits); + } + + // Returns the maximum pixel span of a filter. + int max_filter() const { return max_filter_; } + + // Returns the number of filters in this filter. This is the dimension of the + // output image. + int num_values() const { return static_cast<int>(filters_.size()); } + + // Appends the given list of scaling values for generating a given output + // pixel. |filter_offset| is the distance from the edge of the image to where + // the scaling factors start. The scaling factors apply to the source pixels + // starting from this position, and going for the next |filter_length| pixels. + // + // You will probably want to make sure your input is normalized (that is, + // all entries in |filter_values| sub to one) to prevent affecting the overall + // brighness of the image. + // + // The filter_length must be > 0. + // + // This version will automatically convert your input to fixed point. + void AddFilter(int filter_offset, + const float* filter_values, + int filter_length); + + // Same as the above version, but the input is already fixed point. + void AddFilter(int filter_offset, + const int16* filter_values, + int filter_length); + + // Retrieves a filter for the given |value_offset|, a position in the output + // image in the direction we're convolving. The offset and length of the + // filter values are put into the corresponding out arguments (see AddFilter + // above for what these mean), and a pointer to the first scaling factor is + // returned. There will be |filter_length| values in this array. + inline const int16* FilterForValue(int value_offset, + int* filter_offset, + int* filter_length) const { + const FilterInstance& filter = filters_[value_offset]; + *filter_offset = filter.offset; + *filter_length = filter.length; + return &filter_values_[filter.data_location]; + } + + private: + struct FilterInstance { + // Offset within filter_values for this instance of the filter. + int data_location; + + // Distance from the left of the filter to the center. IN PIXELS + int offset; + + // Number of values in this filter instance. + int length; + }; + + // Stores the information for each filter added to this class. + std::vector<FilterInstance> filters_; + + // We store all the filter values in this flat list, indexed by + // |FilterInstance.data_location| to avoid the mallocs required for storing + // each one separately. + std::vector<int16> filter_values_; + + // The maximum size of any filter we've added. + int max_filter_; +}; + +// Does a two-dimensional convolusion on the given source image. +// +// It is assumed the source pixel offsets referenced in the input filters +// reference only valid pixels, so the source image size is not required. Each +// row of the source image starts |source_byte_row_stride| after the previous +// one (this allows you to have rows with some padding at the end). +// +// The result will be put into the given output buffer. The destination image +// size will be xfilter.num_values() * yfilter.num_values() pixels. It will be +// in rows of exactly xfilter.num_values() * 4 bytes. +// +// |source_has_alpha| is a hint that allows us to avoid doing computations on +// the alpha channel if the image is opaque. If you don't know, set this to +// true and it will work properly, but setting this to false will be a few +// percent faster if you know the image is opaque. +// +// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order +// (this is ARGB when loaded into 32-bit words on a little-endian machine). +void BGRAConvolve2D(const uint8* source_data, + int source_byte_row_stride, + bool source_has_alpha, + const ConvolusionFilter1D& xfilter, + const ConvolusionFilter1D& yfilter, + uint8* output); + +} // namespace gfx + +#endif // BASE_GFX_CONVOLVER_H__ diff --git a/base/gfx/convolver_unittest.cc b/base/gfx/convolver_unittest.cc new file mode 100644 index 0000000..b9d210a --- /dev/null +++ b/base/gfx/convolver_unittest.cc @@ -0,0 +1,151 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string.h> +#include <time.h> +#include <vector> + +#include "base/gfx/convolver.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +namespace { + +// Fills the given filter with impulse functions for the range 0->num_entries. + void FillImpulseFilter(int num_entries, ConvolusionFilter1D* filter) { + float one = 1.0f; + for (int i = 0; i < num_entries; i++) + filter->AddFilter(i, &one, 1); +} + +// Filters the given input with the impulse function, and verifies that it +// does not change. +void TestImpulseConvolusion(const unsigned char* data, int width, int height) { + int byte_count = width * height * 4; + + ConvolusionFilter1D filter_x; + FillImpulseFilter(width, &filter_x); + + ConvolusionFilter1D filter_y; + FillImpulseFilter(height, &filter_y); + + std::vector<unsigned char> output; + output.resize(byte_count); + BGRAConvolve2D(data, width * 4, true, filter_x, filter_y, &output[0]); + + // Output should exactly match input. + EXPECT_EQ(0, memcmp(data, &output[0], byte_count)); +} + +// Fills the destination filter with a box filter averaging every two pixels +// to produce the output. +void FillBoxFilter(int size, ConvolusionFilter1D* filter) { + const float box[2] = { 0.5, 0.5 }; + for (int i = 0; i < size; i++) + filter->AddFilter(i * 2, box, 2); +} + +} // namespace + +// Tests that each pixel, when set and run through the impulse filter, does +// not change. +TEST(Convolver, Impulse) { + // We pick an "odd" size that is not likely to fit on any boundaries so that + // we can see if all the widths and paddings are handled properly. + int width = 15; + int height = 31; + int byte_count = width * height * 4; + std::vector<unsigned char> input; + input.resize(byte_count); + + unsigned char* input_ptr = &input[0]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + for (int channel = 0; channel < 3; channel++) { + memset(input_ptr, 0, byte_count); + input_ptr[(y * width + x) * 4 + channel] = 0xff; + // Always set the alpha channel or it will attempt to "fix" it for us. + input_ptr[(y * width + x) * 4 + 3] = 0xff; + TestImpulseConvolusion(input_ptr, width, height); + } + } + } +} + +// Tests that using a box filter to halve an image results in every square of 4 +// pixels in the original get averaged to a pixel in the output. +TEST(Convolver, Halve) { + static const int kSize = 16; + + int src_width = kSize; + int src_height = kSize; + int src_row_stride = src_width * 4; + int src_byte_count = src_row_stride * src_height; + std::vector<unsigned char> input; + input.resize(src_byte_count); + + int dest_width = src_width / 2; + int dest_height = src_height / 2; + int dest_byte_count = dest_width * dest_height * 4; + std::vector<unsigned char> output; + output.resize(dest_byte_count); + + // First fill the array with a bunch of random data. + srand(static_cast<unsigned>(time(NULL))); + for (int i = 0; i < src_byte_count; i++) + input[i] = rand() * 255 / RAND_MAX; + + // Compute the filters. + ConvolusionFilter1D filter_x, filter_y; + FillBoxFilter(dest_width, &filter_x); + FillBoxFilter(dest_height, &filter_y); + + // Do the convolusion. + BGRAConvolve2D(&input[0], src_width, true, filter_x, filter_y, &output[0]); + + // Compute the expected results and check, allowing for a small difference + // to account for rounding errors. + for (int y = 0; y < dest_height; y++) { + for (int x = 0; x < dest_width; x++) { + for (int channel = 0; channel < 4; channel++) { + int src_offset = (y * 2 * src_row_stride + x * 2 * 4) + channel; + int value = input[src_offset] + // Top left source pixel. + input[src_offset + 4] + // Top right source pixel. + input[src_offset + src_row_stride] + // Lower left. + input[src_offset + src_row_stride + 4]; // Lower right. + value /= 4; // Average. + int difference = value - output[(y * dest_width + x) * 4 + channel]; + EXPECT_TRUE(difference >= -1 || difference <= 1); + } + } + } +} + +} // namespace gfx
\ No newline at end of file diff --git a/base/gfx/font_utils.cc b/base/gfx/font_utils.cc new file mode 100644 index 0000000..340dbbc --- /dev/null +++ b/base/gfx/font_utils.cc @@ -0,0 +1,305 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/font_utils.h" + +#include <limits> +#include <map> + +#include "base/gfx/uniscribe.h" +#include "base/logging.h" +#include "base/singleton.h" +#include "base/string_util.h" +#include "unicode/locid.h" + +namespace gfx { + +namespace { + +// hash_map has extra cost with no sizable gain for a small number of integer +// key items. When the map size becomes much bigger (which will be later as +// more scripts are added) and this turns out to be prominent in the profile, we +// may consider switching to hash_map (or just an array if we support all the +// scripts) +typedef std::map<UScriptCode, const wchar_t*> ScriptToFontMap; + +struct ScriptToFontMapSingletonTraits + : public DefaultSingletonTraits<ScriptToFontMap> { + static ScriptToFontMap* New() { + struct FontMap { + UScriptCode script; + const wchar_t* family; + }; + + const static FontMap font_map[] = { + {USCRIPT_SIMPLIFIED_HAN, L"simsun"}, + {USCRIPT_TRADITIONAL_HAN, L"pmingliu"}, + {USCRIPT_HIRAGANA, L"ms pgothic"}, + {USCRIPT_KATAKANA, L"ms pgothic"}, + {USCRIPT_KATAKANA_OR_HIRAGANA, L"ms pgothic"}, + {USCRIPT_HANGUL, L"gulim"}, + {USCRIPT_THAI, L"tahoma"}, + {USCRIPT_HEBREW, L"david"}, + {USCRIPT_ARABIC, L"simplified arabic"}, + {USCRIPT_DEVANAGARI, L"mangal"}, + {USCRIPT_BENGALI, L"vrinda"}, + {USCRIPT_GURMUKHI, L"raavi"}, + {USCRIPT_GUJARATI, L"shruti"}, + {USCRIPT_ORIYA, L"kalinga"}, + {USCRIPT_TAMIL, L"latha"}, + {USCRIPT_TELUGU, L"gautami"}, + {USCRIPT_KANNADA, L"tunga"}, + {USCRIPT_MALAYALAM, L"kartika"}, + {USCRIPT_LAO, L"dokchampa"}, + {USCRIPT_TIBETAN, L"microsoft himalaya"}, + {USCRIPT_GEORGIAN, L"sylfaen"}, + {USCRIPT_ARMENIAN, L"sylfaen"}, + {USCRIPT_ETHIOPIC, L"nyala"}, + {USCRIPT_CANADIAN_ABORIGINAL, L"euphemia"}, + {USCRIPT_CHEROKEE, L"plantagenet cherokee"}, + {USCRIPT_YI, L"microsoft yi balti"}, + {USCRIPT_SINHALA, L"iskoola pota"}, + {USCRIPT_SYRIAC, L"estrangelo edessa"}, + {USCRIPT_KHMER, L"daunpenh"}, + {USCRIPT_THAANA, L"mv boli"}, + {USCRIPT_MONGOLIAN, L"mongolian balti"}, + // For common, perhaps we should return a font + // for the current application/system locale. + //{USCRIPT_COMMON, L"times new roman"} + }; + + ScriptToFontMap* new_instance = new ScriptToFontMap; + // Cannot recover from OOM so that there's no need to check. + for (int i = 0; i < arraysize(font_map); ++i) + (*new_instance)[font_map[i].script] = font_map[i].family; + + // Initialize the locale-dependent mapping. + // Since Chrome synchronizes the ICU default locale with its UI locale, + // this ICU locale tells the current UI locale of Chrome. + Locale locale = Locale::getDefault(); + ScriptToFontMap::const_iterator iter; + if (locale == Locale::getKorean()) { + iter = new_instance->find(USCRIPT_HANGUL); + } else if (locale == Locale::getJapanese()) { + iter = new_instance->find(USCRIPT_KATAKANA_OR_HIRAGANA); + } else if (locale == Locale::getTraditionalChinese()) { + iter = new_instance->find(USCRIPT_TRADITIONAL_HAN); + } else { + iter = new_instance->find(USCRIPT_SIMPLIFIED_HAN); + } + if (iter != new_instance->end()) + (*new_instance)[USCRIPT_HAN] = iter->second; + + return new_instance; + } +}; + +Singleton<ScriptToFontMap, ScriptToFontMapSingletonTraits> script_font_map; + +const int kUndefinedAscent = std::numeric_limits<int>::min(); + +// Given an HFONT, return the ascent. If GetTextMetrics fails, +// kUndefinedAscent is returned, instead. +int GetAscent(HFONT hfont) { + HDC dc = GetDC(NULL); + HGDIOBJ oldFont = SelectObject(dc, hfont); + TEXTMETRIC tm; + BOOL got_metrics = GetTextMetrics(dc, &tm); + SelectObject(dc, oldFont); + ReleaseDC(NULL, dc); + return got_metrics ? tm.tmAscent : kUndefinedAscent; +} + +struct FontData { + FontData() : hfont(NULL), ascent(kUndefinedAscent), script_cache(NULL) {} + HFONT hfont; + int ascent; + mutable SCRIPT_CACHE script_cache; +}; + +// Again, using hash_map does not earn us much here. +// page_cycler_test intl2 gave us a 'better' result with map than with hash_map +// even though they're well-within 1-sigma of each other so that the difference +// is not significant. On the other hand, some pages in intl2 seem to +// take longer to load with map in the 1st pass. Need to experiment further. +typedef std::map<std::wstring, FontData*> FontDataCache; +struct FontDataCacheSingletonTraits + : public DefaultSingletonTraits<FontDataCache> { + static void Delete(FontDataCache* cache) { + FontDataCache::iterator iter = cache->begin(); + while (iter != cache->end()) { + SCRIPT_CACHE script_cache = iter->second->script_cache; + if (script_cache) + ScriptFreeCache(&script_cache); + delete iter->second; + ++iter; + } + delete cache; + } +}; + +} // namespace + +// TODO(jungshik) : this is font fallback code version 0.1 +// - Cover all the scripts +// - Get the default font for each script/generic family from the +// preference instead of hardcoding in the source. +// - Support generic families (from FontDescription) +// - If the default font for a script is not available, +// use EnumFontFamilies or similar APIs to come up with a list of +// fonts supporting the script and cache the result. +// - Consider using UnicodeSet (or UnicodeMap) to keep track of which +// character is supported by which font +// - Update script_font_cache in response to WM_FONTCHANGE + +const wchar_t* GetFontFamilyForScript(UScriptCode script, + GenericFamilyType generic) { + ScriptToFontMap::const_iterator iter = script_font_map->find(script); + const wchar_t* family = NULL; + if (iter != script_font_map->end()) { + family = iter->second; + } + return family; +} + +// TODO(jungshik) +// - Handle 'Inherited', 'Common' and 'Unknown' +// (see http://www.unicode.org/reports/tr24/#Usage_Model ) +// For 'Inherited' and 'Common', perhaps we need to +// accept another parameter indicating the previous family +// and just return it. +// - All the characters (or characters up to the point a single +// font can cover) need to be taken into account +const wchar_t* GetFallbackFamily(const wchar_t* characters, + int length, + GenericFamilyType generic) { + DCHECK(characters && characters[0] && length > 0); + UScriptCode script = USCRIPT_COMMON; + + // Sometimes characters common to script (e.g. space) is at + // the beginning of a string so that we need to skip them + // to get a font required to render the string. + int i = 0; + UChar32 ucs4 = 0; + while (i < length && script == USCRIPT_COMMON || + script == USCRIPT_INVALID_CODE) { + U16_NEXT(characters, i, length, ucs4); + UErrorCode err = U_ZERO_ERROR; + script = uscript_getScript(ucs4, &err); + // silently ignore the error + } + + // hack for full width ASCII. Japanese are most fond of full-width ASCII. + // TODO(jungshik) find a better way ! + if (0xFF00 < ucs4 && ucs4 < 0xFF5F) + return L"ms pgothic"; + + const wchar_t* family = GetFontFamilyForScript(script, generic); + if (!family) { + int plane = ucs4 >> 16; + switch (plane) { + case 1: + family = L"code2001"; + break; + case 2: + family = L"simsun ext b"; + break; + default: + family = L"arial unicode ms"; + } + } + + return family; +} + + + +// Be aware that this is not thread-safe. +bool GetDerivedFontData(const wchar_t *family, + int style, + LOGFONT *logfont, + int *ascent, + HFONT *hfont, + SCRIPT_CACHE **script_cache) { + DCHECK(logfont && family && *family); + // Using |Singleton| here is not free, but the intl2 page cycler test + // does not show any noticeable difference with and without it. Leaking + // the contents of FontDataCache (especially SCRIPT_CACHE) at the end + // of a renderer process may not be a good idea. We may use + // atexit(). However, with no noticeable performance difference, |Singleton| + // is cleaner, I believe. + FontDataCache* font_data_cache = + Singleton<FontDataCache, FontDataCacheSingletonTraits>::get(); + // TODO(jungshik) : This comes up pretty high in the profile so that + // we need to measure whether using SHA256 (after coercing all the + // fields to char*) is faster than StringPrintf. + std::wstring font_key = StringPrintf(L"%1d:%d:%s", style, logfont->lfHeight, + family); + FontDataCache::const_iterator iter = font_data_cache->find(font_key); + FontData *derived; + if (iter == font_data_cache->end()) { + DCHECK(wcslen(family) < LF_FACESIZE); + wcscpy_s(logfont->lfFaceName, LF_FACESIZE, family); + // TODO(jungshik): CreateFontIndirect always comes up with + // a font even if there's no font matching the name. Need to + // check it against what we actually want (as is done in FontCacheWin.cpp) + derived = new FontData; + derived->hfont = CreateFontIndirect(logfont); + // GetAscent may return kUndefinedAscent, but we still want to + // cache it so that we won't have to call CreateFontIndirect once + // more for HFONT next time. + derived->ascent = GetAscent(derived->hfont); + (*font_data_cache)[font_key] = derived; + } else { + derived = iter->second; + // Last time, GetAscent failed so that only HFONT was + // cached. Try once more assuming that TryPreloadFont + // was called by a caller between calls. + if (kUndefinedAscent == derived->ascent) + derived->ascent = GetAscent(derived->hfont); + } + *hfont = derived->hfont; + *ascent = derived->ascent; + *script_cache = &(derived->script_cache); + return *ascent != kUndefinedAscent; +} + +int GetStyleFromLogfont(const LOGFONT* logfont) { + // TODO(jungshik) : consider defining UNDEFINED or INVALID for style and + // returning it when logfont is NULL + if (!logfont) { + NOTREACHED(); + return FONT_STYLE_NORMAL; + } + return (logfont->lfItalic ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL) | + (logfont->lfUnderline ? FONT_STYLE_UNDERLINED : FONT_STYLE_NORMAL) | + (logfont->lfWeight >= 700 ? FONT_STYLE_BOLD : FONT_STYLE_NORMAL); +} + +} // namespace gfx diff --git a/base/gfx/font_utils.h b/base/gfx/font_utils.h new file mode 100644 index 0000000..e06b588 --- /dev/null +++ b/base/gfx/font_utils.h @@ -0,0 +1,105 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// A collection of utilities for font handling. + +#ifndef BASE_GFX_FONT_UTILS_H__ +#define BASE_GFX_FONT_UTILS_H__ + +#include <usp10.h> +#include <wchar.h> +#include <windows.h> + +#include <unicode/uscript.h> + +namespace gfx { + +// The order of family types needs to be exactly the same as +// WebCore::FontDescription::GenericFamilyType. We may lift that restriction +// when we make webkit_glue::WebkitGenericToChromeGenericFamily more +// intelligent. +enum GenericFamilyType { + GENERIC_FAMILY_NONE = 0, + GENERIC_FAMILY_STANDARD, + GENERIC_FAMILY_SERIF, + GENERIC_FAMILY_SANSSERIF, + GENERIC_FAMILY_MONOSPACE, + GENERIC_FAMILY_CURSIVE, + GENERIC_FAMILY_FANTASY +}; + +// Return a font family that supports a script and belongs to |generic| font family. +// It can retun NULL and a caller has to implement its own fallback. +const wchar_t* GetFontFamilyForScript(UScriptCode script, + GenericFamilyType generic); + +// Return a font family that can render |characters| based on +// what script characters belong to. +const wchar_t* GetFallbackFamily(const wchar_t *characters, int length, + GenericFamilyType generic); + +// Derive a new HFONT by replacing lfFaceName of LOGFONT with |family|, +// calculate the ascent for the derived HFONT, and initialize SCRIPT_CACHE +// in FontData. +// |style| is only used for cache key generation. |style| is +// bit-wise OR of BOLD(1), UNDERLINED(2) and ITALIC(4) and +// should match what's contained in LOGFONT. It should be calculated +// by calling GetStyleFromLogFont. +// Returns false if the font is not accessible, in which case |ascent| field +// of |fontdata| is set to kUndefinedAscent. +// Be aware that this is not thread-safe. +// TODO(jungshik): Instead of having three out params, we'd better have one +// (|*FontData|), but somehow it mysteriously messes up the layout for +// certain complex script pages (e.g. hi.wikipedia.org) and also crashes +// at the start-up if recently visited page list includes pages with complex +// scripts in their title. Moreover, somehow the very first-pass of +// intl2 page-cycler test is noticeably slower with one out param than +// the current version although the subsequent 9 passes take about the +// same time. +bool GetDerivedFontData(const wchar_t *family, + int style, + LOGFONT *logfont, + int *ascent, + HFONT *hfont, + SCRIPT_CACHE **script_cache); + +enum { + FONT_STYLE_NORMAL = 0, + FONT_STYLE_BOLD = 1, + FONT_STYLE_ITALIC = 2, + FONT_STYLE_UNDERLINED = 4 +}; + +// Derive style (bit-wise OR of FONT_STYLE_BOLD, FONT_STYLE_UNDERLINED, and +// FONT_STYLE_ITALIC) from LOGFONT. Returns 0 if |*logfont| is NULL. +int GetStyleFromLogfont(const LOGFONT *logfont); + +} // namespace gfx + +#endif // BASE_GFX_FONT_UTILS_H__ diff --git a/base/gfx/image_operations.cc b/base/gfx/image_operations.cc new file mode 100644 index 0000000..4dde08c --- /dev/null +++ b/base/gfx/image_operations.cc @@ -0,0 +1,387 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#define _USE_MATH_DEFINES +#include <cmath> +#include <limits> +#include <vector> + +#include "base/gfx/image_operations.h" + +#include "base/gfx/convolver.h" +#include "base/gfx/rect.h" +#include "base/gfx/size.h" +#include "base/logging.h" +#include "base/stack_container.h" +#include "SkBitmap.h" + +namespace gfx { + +namespace { + +// Returns the ceiling/floor as an integer. +inline int CeilInt(float val) { + return static_cast<int>(ceil(val)); +} +inline int FloorInt(float val) { + return static_cast<int>(floor(val)); +} + +// Filter function computation ------------------------------------------------- + +// Evaluates the box filter, which goes from -0.5 to +0.5. +float EvalBox(float x) { + return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f; +} + +// Evaluates the Lanczos filter of the given filter size window for the given +// position. +// +// |filter_size| is the width of the filter (the "window"), outside of which +// the value of the function is 0. Inside of the window, the value is the +// normalized sinc function: +// lanczos(x) = sinc(x) * sinc(x / filter_size); +// where +// sinc(x) = sin(pi*x) / (pi*x); +float EvalLanczos(int filter_size, float x) { + if (x <= -filter_size || x >= filter_size) + return 0.0f; // Outside of the window. + if (x > -std::numeric_limits<float>::epsilon() && + x < std::numeric_limits<float>::epsilon()) + return 1.0f; // Special case the discontinuity at the origin. + float xpi = x * static_cast<float>(M_PI); + return (sin(xpi) / xpi) * // sinc(x) + sin(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size) +} + +// ResizeFilter ---------------------------------------------------------------- + +// Encapsulates computation and storage of the filters required for one complete +// resize operation. +class ResizeFilter { + public: + ResizeFilter(ImageOperations::ResizeMethod method, + const Size& src_full_size, + const Size& dest_size, + const Rect& dest_subset); + + // Returns the bounds in the input bitmap of data that is used in the output. + // The filter offsets are within this rectangle. + const Rect& src_depend() { return src_depend_; } + + // Returns the filled filter values. + const ConvolusionFilter1D& x_filter() { return x_filter_; } + const ConvolusionFilter1D& y_filter() { return y_filter_; } + + private: + // Returns the number of pixels that the filer spans, in filter space (the + // destination image). + float GetFilterSupport(float scale) { + switch (method_) { + case ImageOperations::RESIZE_BOX: + // The box filter just scales with the image scaling. + return 0.5f; // Only want one side of the filter = /2. + case ImageOperations::RESIZE_LANCZOS3: + // The lanczos filter takes as much space in the source image in + // each direction as the size of the window = 3 for Lanczos3. + return 3.0f; + default: + NOTREACHED(); + return 1.0f; + } + } + + // Computes one set of filters either horizontally or vertically. The caller + // will specify the "min" and "max" rather than the bottom/top and + // right/bottom so that the same code can be re-used in each dimension. + // + // |src_depend_lo| and |src_depend_size| gives the range for the source + // depend rectangle (horizontally or vertically at the caller's discretion + // -- see above for what this means). + // + // Likewise, the range of destination values to compute and the scale factor + // for the transform is also specified. + void ComputeFilters(int src_size, + int dest_subset_lo, int dest_subset_size, + float scale, float src_support, + ConvolusionFilter1D* output); + + // Computes the filter value given the coordinate in filter space. + inline float ComputeFilter(float pos) { + switch (method_) { + case ImageOperations::RESIZE_BOX: + return EvalBox(pos); + case ImageOperations::RESIZE_LANCZOS3: + return EvalLanczos(3, pos); + default: + NOTREACHED(); + return 0; + } + } + + ImageOperations::ResizeMethod method_; + + // Subset of source the filters will touch. + Rect src_depend_; + + // Size of the filter support on one side only in the destination space. + // See GetFilterSupport. + float x_filter_support_; + float y_filter_support_; + + // Subset of scaled destination bitmap to compute. + Rect out_bounds_; + + ConvolusionFilter1D x_filter_; + ConvolusionFilter1D y_filter_; + + DISALLOW_EVIL_CONSTRUCTORS(ResizeFilter); +}; + +ResizeFilter::ResizeFilter(ImageOperations::ResizeMethod method, + const Size& src_full_size, + const Size& dest_size, + const Rect& dest_subset) + : method_(method), + out_bounds_(dest_subset) { + float scale_x = static_cast<float>(dest_size.width()) / + static_cast<float>(src_full_size.width()); + float scale_y = static_cast<float>(dest_size.height()) / + static_cast<float>(src_full_size.height()); + + x_filter_support_ = GetFilterSupport(scale_x); + y_filter_support_ = GetFilterSupport(scale_y); + + gfx::Rect src_full(0, 0, src_full_size.width(), src_full_size.height()); + gfx::Rect dest_full(0, 0, + static_cast<int>(src_full_size.width() * scale_x + 0.5), + static_cast<int>(src_full_size.height() * scale_y + 0.5)); + + // Support of the filter in source space. + float src_x_support = x_filter_support_ / scale_x; + float src_y_support = y_filter_support_ / scale_y; + + ComputeFilters(src_full_size.width(), dest_subset.x(), dest_subset.width(), + scale_x, src_x_support, &x_filter_); + ComputeFilters(src_full_size.height(), dest_subset.y(), dest_subset.height(), + scale_y, src_y_support, &y_filter_); +} + +void ResizeFilter::ComputeFilters(int src_size, + int dest_subset_lo, int dest_subset_size, + float scale, float src_support, + ConvolusionFilter1D* output) { + int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi) + + // When we're doing a magnification, the scale will be larger than one. This + // means the destination pixels are much smaller than the source pixels, and + // that the range covered by the filter won't necessarily cover any source + // pixel boundaries. Therefore, we use these clamped values (max of 1) for + // some computations. + float clamped_src_support = std::min(1.0f, src_support); + float clamped_scale = std::min(1.0f, scale); + + // Speed up the divisions below by turning them into multiplies. + float inv_scale = 1.0f / scale; + + StackVector<float, 64> filter_values; + StackVector<int16, 64> fixed_filter_values; + + // Loop over all pixels in the output range. We will generate one set of + // filter values for each one. Those values will tell us how to blend the + // source pixels to compute the destination pixel. + for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi; + dest_subset_i++) { + // Reset the arrays. We don't declare them inside so they can re-use the + // same malloc-ed buffer. + filter_values->clear(); + fixed_filter_values->clear(); + + // This is the pixel in the source directly under the pixel in the dest. + float src_pixel = dest_subset_i * inv_scale; + + // Compute the (inclusive) range of source pixels the filter covers. + int src_begin = std::max(0, FloorInt(src_pixel - src_support)); + int src_end = std::min(src_size - 1, CeilInt(src_pixel + src_support)); + + // Compute the unnormalized filter value at each location of the source + // it covers. + float filter_sum = 0.0f; // Sub of the filter values for normalizing. + for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end; + cur_filter_pixel++) { + // Distance from the center of the filter, this is the filter coordinate + // in source space. + float src_filter_pos = cur_filter_pixel - src_pixel; + + // Since the filter really exists in dest space, map it there. + float dest_filter_pos = src_filter_pos * clamped_scale; + + // Compute the filter value at that location. + float filter_value = ComputeFilter(dest_filter_pos); + filter_values->push_back(filter_value); + + filter_sum += filter_value; + } + DCHECK(!filter_values->empty()) << "We should always get a filter!"; + + // The filter must be normalized so that we don't affect the brightness of + // the image. Convert to normalized fixed point. + int16 fixed_sum = 0; + for (size_t i = 0; i < filter_values->size(); i++) { + int16 cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum); + fixed_sum += cur_fixed; + fixed_filter_values->push_back(cur_fixed); + } + + // The conversion to fixed point will leave some rounding errors, which + // we add back in to avoid affecting the brightness of the image. We + // arbitrarily add this to the center of the filter array (this won't always + // be the center of the filter function since it could get clipped on the + // edges, but it doesn't matter enough to worry about that case). + int16 leftovers = output->FloatToFixed(1.0f) - fixed_sum; + fixed_filter_values[fixed_filter_values->size() / 2] += leftovers; + + // Now it's ready to go. + output->AddFilter(src_begin, &fixed_filter_values[0], + static_cast<int>(fixed_filter_values->size())); + } +} + +} // namespace + +// Resize ---------------------------------------------------------------------- + +// static +SkBitmap ImageOperations::Resize(const SkBitmap& source, + ResizeMethod method, + const Size& dest_size, + const Rect& dest_subset) { + DCHECK(Rect(dest_size.width(), dest_size.height()).Contains(dest_subset)) << + "The supplied subset does not fall within the destination image."; + + // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just + // return empty + if (source.width() < 1 || source.height() < 1 || + dest_size.width() < 1 || dest_size.height() < 1) + return SkBitmap(); + + SkAutoLockPixels locker(source); + + ResizeFilter filter(method, Size(source.width(), source.height()), + dest_size, dest_subset); + + // Get a source bitmap encompassing this touched area. We construct the + // offsets and row strides such that it looks like a new bitmap, while + // referring to the old data. + const uint8* source_subset = + reinterpret_cast<const uint8*>(source.getPixels()); + + // Convolve into the result. + SkBitmap result; + result.setConfig(SkBitmap::kARGB_8888_Config, + dest_subset.width(), dest_subset.height()); + result.allocPixels(); + BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()), + !source.isOpaque(), filter.x_filter(), filter.y_filter(), + static_cast<unsigned char*>(result.getPixels())); + + // Preserve the "opaque" flag for use as an optimization later. + result.setIsOpaque(source.isOpaque()); + + return result; +} + +// static +SkBitmap ImageOperations::Resize(const SkBitmap& source, + ResizeMethod method, + const Size& dest_size) { + Rect dest_subset(0, 0, dest_size.width(), dest_size.height()); + return Resize(source, method, dest_size, dest_subset); +} + +// static +SkBitmap ImageOperations::CreateBlendedBitmap(const SkBitmap& first, + const SkBitmap& second, + double alpha) { + DCHECK(alpha <= 1 && alpha >= 0); + DCHECK(first.width() == second.width()); + DCHECK(first.height() == second.height()); + DCHECK(first.bytesPerPixel() == second.bytesPerPixel()); + DCHECK(first.config() == SkBitmap::kARGB_8888_Config); + + // Optimize for case where we won't need to blend anything. + static const double alpha_min = 1.0 / 255; + static const double alpha_max = 254.0 / 255; + if (alpha < alpha_min) { + return first; + } else if (alpha > alpha_max) { + return second; + } + + SkAutoLockPixels lock_first(first); + SkAutoLockPixels lock_second(second); + + SkBitmap blended; + blended.setConfig(SkBitmap::kARGB_8888_Config, first.width(), + first.height(), 0); + blended.allocPixels(); + blended.eraseARGB(0, 0, 0, 0); + + double first_alpha = 1 - alpha; + + for (int y = 0; y < first.height(); y++) { + uint32* first_row = first.getAddr32(0, y); + uint32* second_row = second.getAddr32(0, y); + uint32* dst_row = blended.getAddr32(0, y); + + for (int x = 0; x < first.width(); x++) { + uint32 first_pixel = first_row[x]; + uint32 second_pixel = second_row[x]; + + int a = static_cast<int>( + SkColorGetA(first_pixel) * first_alpha + + SkColorGetA(second_pixel) * alpha); + int r = static_cast<int>( + SkColorGetR(first_pixel) * first_alpha + + SkColorGetR(second_pixel) * alpha); + int g = static_cast<int>( + SkColorGetG(first_pixel) * first_alpha + + SkColorGetG(second_pixel) * alpha); + int b = static_cast<int>( + SkColorGetB(first_pixel) * first_alpha + + SkColorGetB(second_pixel) * alpha); + + dst_row[x] = SkColorSetARGB(a, r, g, b); + } + } + + return blended; +} + +} // namespace gfx diff --git a/base/gfx/image_operations.h b/base/gfx/image_operations.h new file mode 100644 index 0000000..5d3eeef --- /dev/null +++ b/base/gfx/image_operations.h @@ -0,0 +1,87 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_IMAGE_OPERATIONS_H__ +#define BASE_GFX_IMAGE_OPERATIONS_H__ + +#include "base/basictypes.h" +#include "base/gfx/rect.h" + +class SkBitmap; + +namespace gfx { + +class ImageOperations { + public: + enum ResizeMethod { + // Box filter. This is a weighted average of all of the pixels touching + // the destination pixel. For enlargement, this is nearest neighbor. + // + // You probably don't want this, it is here for testing since it is easy to + // compute. Use RESIZE_LANCZOS3 instead. + RESIZE_BOX, + + // 3-cycle Lanczos filter. This is tall in the middle, goes negative on + // each side, then oscillates 2 more times. It gives nice sharp edges. + RESIZE_LANCZOS3, + }; + + // Resizes the given source bitmap using the specified resize method, so that + // the entire image is (dest_size) big. The dest_subset is the rectangle in + // this destination image that should actually be returned. + // + // The output image will be (dest_subset.width(), dest_subset.height()). This + // will save work if you do not need the entire bitmap. + // + // The destination subset must be smaller than the destination image. + static SkBitmap Resize(const SkBitmap& source, + ResizeMethod method, + const Size& dest_size, + const Rect& dest_subset); + + // Alternate version for resizing and returning the entire bitmap rather than + // a subset. + static SkBitmap Resize(const SkBitmap& source, + ResizeMethod method, + const Size& dest_size); + + + // Create a bitmap that is a blend of two others. The alpha argument + // specifies the opacity of the second bitmap. The provided bitmaps must + // use have the kARGB_8888_Config config and be of equal dimensions. + static SkBitmap CreateBlendedBitmap(const SkBitmap& first, + const SkBitmap& second, + double alpha); + private: + ImageOperations(); // Class for scoping only. +}; + +} // namespace gfx + +#endif // BASE_GFX_IMAGE_OPERATIONS_H__ diff --git a/base/gfx/image_operations_unittest.cc b/base/gfx/image_operations_unittest.cc new file mode 100644 index 0000000..1c2b40f --- /dev/null +++ b/base/gfx/image_operations_unittest.cc @@ -0,0 +1,172 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdlib.h> + +#include "base/gfx/image_operations.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "SkBitmap.h" + +namespace { + +// Computes the average pixel value for the given range, inclusive. +uint32_t AveragePixel(const SkBitmap& bmp, + int x_min, int x_max, + int y_min, int y_max) { + float accum[4] = {0, 0, 0, 0}; + int count = 0; + for (int y = y_min; y <= y_max; y++) { + for (int x = x_min; x <= x_max; x++) { + uint32_t cur = *bmp.getAddr32(x, y); + accum[0] += SkColorGetB(cur); + accum[1] += SkColorGetG(cur); + accum[2] += SkColorGetR(cur); + accum[3] += SkColorGetA(cur); + count++; + } + } + + return SkColorSetARGB(static_cast<unsigned char>(accum[3] / count), + static_cast<unsigned char>(accum[2] / count), + static_cast<unsigned char>(accum[1] / count), + static_cast<unsigned char>(accum[0] / count)); +} + +// Returns true if each channel of the given two colors are "close." This is +// used for comparing colors where rounding errors may cause off-by-one. +bool ColorsClose(uint32_t a, uint32_t b) { + return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 && + abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 && + abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2 && + abs(static_cast<int>(SkColorGetA(a) - SkColorGetA(b))) < 2; +} + +void FillDataToBitmap(int w, int h, SkBitmap* bmp) { + bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bmp->allocPixels(); + + unsigned char* src_data = + reinterpret_cast<unsigned char*>(bmp->getAddr32(0, 0)); + for (int i = 0; i < w * h; i++) { + src_data[i * 4 + 0] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 1] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 2] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 3] = static_cast<unsigned char>(i % 255); + } +} + +} // namespace + +// Makes the bitmap 50% the size as the original using a box filter. This is +// an easy operation that we can check the results for manually. +TEST(ImageOperations, Halve) { + // Make our source bitmap. + int src_w = 30, src_h = 38; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Do a halving of the full bitmap. + SkBitmap actual_results = gfx::ImageOperations::Resize( + src, gfx::ImageOperations::RESIZE_BOX, gfx::Size(src_w / 2, src_h / 2)); + ASSERT_EQ(src_w / 2, actual_results.width()); + ASSERT_EQ(src_h / 2, actual_results.height()); + + // Compute the expected values & compare. + SkAutoLockPixels lock(actual_results); + for (int y = 0; y < actual_results.height(); y++) { + for (int x = 0; x < actual_results.width(); x++) { + int first_x = std::max(0, x * 2 - 1); + int last_x = std::min(src_w - 1, x * 2); + + int first_y = std::max(0, y * 2 - 1); + int last_y = std::min(src_h - 1, y * 2); + + uint32_t expected_color = AveragePixel(src, + first_x, last_x, first_y, last_y); + EXPECT_TRUE(ColorsClose(expected_color, *actual_results.getAddr32(x, y))); + } + } +} + +TEST(ImageOperations, HalveSubset) { + // Make our source bitmap. + int src_w = 16, src_h = 34; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Do a halving of the full bitmap. + SkBitmap full_results = gfx::ImageOperations::Resize( + src, gfx::ImageOperations::RESIZE_BOX, gfx::Size(src_w / 2, src_h / 2)); + ASSERT_EQ(src_w / 2, full_results.width()); + ASSERT_EQ(src_h / 2, full_results.height()); + + // Now do a halving of a a subset, recall the destination subset is in the + // destination coordinate system (max = half of the original image size). + gfx::Rect subset_rect(2, 3, 3, 6); + SkBitmap subset_results = gfx::ImageOperations::Resize( + src, gfx::ImageOperations::RESIZE_BOX, + gfx::Size(src_w / 2, src_h / 2), subset_rect); + ASSERT_EQ(subset_rect.width(), subset_results.width()); + ASSERT_EQ(subset_rect.height(), subset_results.height()); + + // The computed subset and the corresponding subset of the original image + // should be the same. + SkAutoLockPixels full_lock(full_results); + SkAutoLockPixels subset_lock(subset_results); + for (int y = 0; y < subset_rect.height(); y++) { + for (int x = 0; x < subset_rect.width(); x++) { + ASSERT_EQ( + *full_results.getAddr32(x + subset_rect.x(), y + subset_rect.y()), + *subset_results.getAddr32(x, y)); + } + } +} + +// Resamples an iamge to the same image, it should give almost the same result. +TEST(ImageOperations, ResampleToSame) { + // Make our source bitmap. + int src_w = 16, src_h = 34; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Do a resize of the full bitmap to the same size. The lanczos filter is good + // enough that we should get exactly the same image for output. + SkBitmap results = gfx::ImageOperations::Resize( + src, gfx::ImageOperations::RESIZE_LANCZOS3, gfx::Size(src_w, src_h)); + ASSERT_EQ(src_w, results.width()); + ASSERT_EQ(src_h, results.height()); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels results_lock(results); + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_EQ(*src.getAddr32(x, y), *results.getAddr32(x, y)); + } + } +}
\ No newline at end of file diff --git a/base/gfx/img_resize_perftest.cc b/base/gfx/img_resize_perftest.cc new file mode 100644 index 0000000..43f8deb --- /dev/null +++ b/base/gfx/img_resize_perftest.cc @@ -0,0 +1,94 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdlib.h> +#include <time.h> + +#include "base/perftimer.h" +#include "base/gfx/convolver.h" +#include "base/gfx/image_operations.h" +#include "base/gfx/image_resizer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +void FillRandomData(char* dest, int byte_count) { + srand(static_cast<unsigned>(time(NULL))); + for (int i = 0; i < byte_count; i++) + dest[i] = rand() * 255 / RAND_MAX; +} + +} // namespace + +// Old code gives [1521, 1519]ms for this, 4000x4000 -> 2100x2100 lanczos8 + +TEST(ImageResizePerf, BigFilter) { + static const int kSrcWidth = 4000; + static const int kSrcHeight = 4000; + static const int kSrcByteSize = kSrcWidth * kSrcHeight * 4; + + SkBitmap src_bmp; + src_bmp.setConfig(SkBitmap::kARGB_8888_Config, kSrcWidth, kSrcHeight); + src_bmp.allocPixels(); + FillRandomData(reinterpret_cast<char*>(src_bmp.getAddr32(0, 0)), + kSrcByteSize); + + // Make the dest size > 1/2 so the 50% optimization doesn't kick in. + static const int kDestWidth = 1400; + static const int kDestHeight = 1400; + + PerfTimeLogger resize_timer("resize"); + gfx::ImageResizer resizer(gfx::ImageResizer::LANCZOS3); + SkBitmap dest = resizer.Resize(src_bmp, kDestWidth, kDestHeight); +} + +// The original image filter we were using took 523ms for this test, while this +// one takes 857ms. +// TODO(brettw) make this at least 64% faster. +TEST(ImageOperationPerf, BigFilter) { + static const int kSrcWidth = 4000; + static const int kSrcHeight = 4000; + static const int kSrcByteSize = kSrcWidth * kSrcHeight * 4; + + SkBitmap src_bmp; + src_bmp.setConfig(SkBitmap::kARGB_8888_Config, kSrcWidth, kSrcHeight); + src_bmp.allocPixels(); + src_bmp.setIsOpaque(true); + FillRandomData(reinterpret_cast<char*>(src_bmp.getAddr32(0, 0)), + kSrcByteSize); + + // Make the dest size > 1/2 so the 50% optimization doesn't kick in. + static const int kDestWidth = 1400; + static const int kDestHeight = 1400; + + PerfTimeLogger resize_timer("resize"); + SkBitmap dest = gfx::ImageOperations::Resize(src_bmp, + gfx::ImageOperations::RESIZE_LANCZOS3, (float)kDestWidth / (float)kSrcWidth, + (float)kDestHeight / (float)kSrcHeight); +} diff --git a/base/gfx/native_theme.cc b/base/gfx/native_theme.cc new file mode 100644 index 0000000..8ab4ca3 --- /dev/null +++ b/base/gfx/native_theme.cc @@ -0,0 +1,626 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/native_theme.h" + +#include <windows.h> +#include <uxtheme.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "base/gfx/bitmap_header.h" +#include "base/gfx/platform_canvas.h" +#include "base/gfx/skia_utils.h" +#include "base/gfx/rect.h" +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "skia/include/SkShader.h" + +namespace gfx { + +/* static */ +const NativeTheme* NativeTheme::instance() { + // The global NativeTheme instance. + static const NativeTheme s_native_theme; + return &s_native_theme; +} + +NativeTheme::NativeTheme() + : theme_dll_(LoadLibrary(L"uxtheme.dll")), + draw_theme_(NULL), + draw_theme_ex_(NULL), + get_theme_color_(NULL), + get_theme_content_rect_(NULL), + get_theme_part_size_(NULL), + open_theme_(NULL), + close_theme_(NULL), + set_theme_properties_(NULL), + is_theme_active_(NULL), + get_theme_int_(NULL) { + if (theme_dll_) { + draw_theme_ = reinterpret_cast<DrawThemeBackgroundPtr>( + GetProcAddress(theme_dll_, "DrawThemeBackground")); + draw_theme_ex_ = reinterpret_cast<DrawThemeBackgroundExPtr>( + GetProcAddress(theme_dll_, "DrawThemeBackgroundEx")); + get_theme_color_ = reinterpret_cast<GetThemeColorPtr>( + GetProcAddress(theme_dll_, "GetThemeColor")); + get_theme_content_rect_ = reinterpret_cast<GetThemeContentRectPtr>( + GetProcAddress(theme_dll_, "GetThemeBackgroundContentRect")); + get_theme_part_size_ = reinterpret_cast<GetThemePartSizePtr>( + GetProcAddress(theme_dll_, "GetThemePartSize")); + open_theme_ = reinterpret_cast<OpenThemeDataPtr>( + GetProcAddress(theme_dll_, "OpenThemeData")); + close_theme_ = reinterpret_cast<CloseThemeDataPtr>( + GetProcAddress(theme_dll_, "CloseThemeData")); + set_theme_properties_ = reinterpret_cast<SetThemeAppPropertiesPtr>( + GetProcAddress(theme_dll_, "SetThemeAppProperties")); + is_theme_active_ = reinterpret_cast<IsThemeActivePtr>( + GetProcAddress(theme_dll_, "IsThemeActive")); + get_theme_int_ = reinterpret_cast<GetThemeIntPtr>( + GetProcAddress(theme_dll_, "GetThemeInt")); + } + memset(theme_handles_, 0, sizeof(theme_handles_)); +} + +NativeTheme::~NativeTheme() { + if (theme_dll_) { + CloseHandles(); + FreeLibrary(theme_dll_); + } +} + +HRESULT NativeTheme::PaintButton(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const { + HANDLE handle = GetThemeHandle(BUTTON); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + + // Draw it manually. + // All pressed states have both low bits set, and no other states do. + const bool focused = ((state_id & PBS_PRESSED) == PBS_PRESSED); + if ((part_id == BP_PUSHBUTTON) && focused) { + // BP_PUSHBUTTON has a focus rect drawn around the outer edge, and the + // button itself is shrunk by 1 pixel. + HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW); + if (brush) { + FrameRect(hdc, rect, brush); + InflateRect(rect, -1, -1); + } + } + + DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state); + + // BP_RADIOBUTTON, BP_CHECKBOX, BP_GROUPBOX and BP_USERBUTTON have their + // focus drawn over the control. + if ((part_id != BP_PUSHBUTTON) && focused) + DrawFocusRect(hdc, rect); + + return S_OK; +} + +HRESULT NativeTheme::PaintTextField(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect, + COLORREF color, + bool fill_content_area, + bool draw_edges) const { + // TODO(ojan): http://b/1210017 Figure out how to give the ability to + // exclude individual edges from being drawn. + + HANDLE handle = GetThemeHandle(TEXTFIELD); + // TODO(mpcomplete): can we detect if the color is specified by the user, + // and if not, just use the system color? + // CreateSolidBrush() accepts a RGB value but alpha must be 0. + HBRUSH bg_brush = CreateSolidBrush(color); + HRESULT hr; + // DrawThemeBackgroundEx was introduced in XP SP2, so that it's possible + // draw_theme_ex_ is NULL and draw_theme_ is non-null. + if (handle && (draw_theme_ex_ || (draw_theme_ && draw_edges))) { + if (draw_theme_ex_) { + static DTBGOPTS omit_border_options = { + sizeof(DTBGOPTS), + DTBG_OMITBORDER, + {0,0,0,0} + }; + DTBGOPTS* draw_opts = draw_edges ? NULL : &omit_border_options; + hr = draw_theme_ex_(handle, hdc, part_id, state_id, rect, draw_opts); + } else { + hr = draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + } + + // TODO(maruel): Need to be fixed if get_theme_content_rect_ is NULL. + if (fill_content_area && get_theme_content_rect_) { + RECT content_rect; + hr = get_theme_content_rect_(handle, hdc, part_id, state_id, rect, + &content_rect); + FillRect(hdc, &content_rect, bg_brush); + } + } else { + // Draw it manually. + if (draw_edges) + DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + + if (fill_content_area) { + FillRect(hdc, rect, (classic_state & DFCS_INACTIVE) ? + reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1) : bg_brush); + } + hr = S_OK; + } + DeleteObject(bg_brush); + return hr; +} + +HRESULT NativeTheme::PaintMenuList(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const { + HANDLE handle = GetThemeHandle(MENULIST); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + + // Draw it manually. + DrawFrameControl(hdc, rect, DFC_SCROLL, DFCS_SCROLLCOMBOBOX | classic_state); + return S_OK; +} + +HRESULT NativeTheme::PaintScrollbarArrow(HDC hdc, + int state_id, + int classic_state, + RECT* rect) const { + HANDLE handle = GetThemeHandle(SCROLLBAR); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, SBP_ARROWBTN, state_id, rect, NULL); + + // Draw it manually. + DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state); + return S_OK; +} + +HRESULT NativeTheme::PaintScrollbarTrack(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* target_rect, + RECT* align_rect, + PlatformCanvas* canvas) const { + HANDLE handle = GetThemeHandle(SCROLLBAR); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, target_rect, NULL); + + // Draw it manually. + const DWORD colorScrollbar = GetSysColor(COLOR_SCROLLBAR); + const DWORD color3DFace = GetSysColor(COLOR_3DFACE); + if ((colorScrollbar != color3DFace) && + (colorScrollbar != GetSysColor(COLOR_WINDOW))) { + FillRect(hdc, target_rect, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1)); + } else { + // Create a 2x2 checkerboard pattern using the 3D face and highlight + // colors. + SkColor face = COLORREFToSkColor(color3DFace); + SkColor highlight = COLORREFToSkColor(GetSysColor(COLOR_3DHILIGHT)); + SkColor buffer[] = { face, highlight, highlight, face }; + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); + bitmap.setPixels(buffer); + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + + // Draw that pattern into the target rect, setting the origin to the top + // left corner of the scrollbar track (so the checked rect below the thumb + // aligns properly with the portion above the thumb). + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(align_rect->left), + SkIntToScalar(align_rect->top)); + shader->setLocalMatrix(matrix); + SkPaint paint; + paint.setShader(shader)->unref(); + canvas->drawIRect(RECTToSkIRect(*target_rect), paint); + } + if (classic_state & DFCS_PUSHED) + InvertRect(hdc, target_rect); + return S_OK; +} + +HRESULT NativeTheme::PaintScrollbarThumb(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const { + HANDLE handle = GetThemeHandle(SCROLLBAR); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + + // Draw it manually. + if ((part_id == SBP_THUMBBTNHORZ) || (part_id == SBP_THUMBBTNVERT)) + DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT | BF_MIDDLE); + // Classic mode doesn't have a gripper. + return S_OK; +} + +HRESULT NativeTheme::PaintStatusGripper(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const { + HANDLE handle = GetThemeHandle(STATUS); + if (handle && draw_theme_) { + // Paint the status bar gripper. There doesn't seem to be a + // standard gripper in Windows for the space between + // scrollbars. This is pretty close, but it's supposed to be + // painted over a status bar. + return draw_theme_(handle, hdc, SP_GRIPPER, 0, rect, 0); + } + + // Draw a windows classic scrollbar gripper. + DrawFrameControl(hdc, rect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); + return S_OK; +} + +HRESULT NativeTheme::PaintDialogBackground(HDC hdc, bool active, + RECT* rect) const { + HANDLE handle = GetThemeHandle(WINDOW); + if (handle && draw_theme_) { + return draw_theme_(handle, hdc, WP_DIALOG, + active ? FS_ACTIVE : FS_INACTIVE, rect, NULL); + } + + // Classic just renders a flat color background. + FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1)); + return S_OK; +} + +HRESULT NativeTheme::PaintTabPanelBackground(HDC hdc, RECT* rect) const { + HANDLE handle = GetThemeHandle(TAB); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, TABP_BODY, 0, rect, NULL); + + // Classic just renders a flat color background. + FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1)); + return S_OK; +} + +HRESULT NativeTheme::PaintListBackground(HDC hdc, + bool enabled, + RECT* rect) const { + HANDLE handle = GetThemeHandle(LIST); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, 1, TS_NORMAL, rect, NULL); + + // Draw it manually. + HBRUSH bg_brush = GetSysColorBrush(COLOR_WINDOW); + FillRect(hdc, rect, bg_brush); + DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + return S_OK; +} + +bool NativeTheme::IsThemingActive() const { + if (is_theme_active_) + return !!is_theme_active_(); + return false; +} + +HRESULT NativeTheme::PaintMenuArrow(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + MenuArrowDirection arrow_direction, + bool is_highlighted) const { + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) { + if (arrow_direction == RIGHT_POINTING_ARROW) { + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + } else { + // There is no way to tell the uxtheme API to draw a left pointing arrow; + // it doesn't have a flag equivalent to DFCS_MENUARROWRIGHT. But they + // are needed for RTL locales on Vista. So use a memory DC and mirror + // the region with GDI's StretchBlt. + Rect r(*rect); + ScopedHDC mem_dc(CreateCompatibleDC(hdc)); + ScopedBitmap mem_bitmap(CreateCompatibleBitmap(hdc, r.width(), + r.height())); + HGDIOBJ old_bitmap = SelectObject(mem_dc, mem_bitmap); + // Copy and horizontally mirror the background from hdc into mem_dc. Use + // a negative-width source rect, starting at the rightmost pixel. + StretchBlt(mem_dc, 0, 0, r.width(), r.height(), + hdc, r.right()-1, r.y(), -r.width(), r.height(), SRCCOPY); + // Draw the arrow. + RECT theme_rect = {0, 0, r.width(), r.height()}; + HRESULT result = draw_theme_(handle, mem_dc, part_id, + state_id, &theme_rect, NULL); + // Copy and mirror the result back into mem_dc. + StretchBlt(hdc, r.x(), r.y(), r.width(), r.height(), + mem_dc, r.width()-1, 0, -r.width(), r.height(), SRCCOPY); + SelectObject(mem_dc, old_bitmap); + return result; + } + } + + // For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate a + // left pointing arrow. This makes the following 'if' statement slightly + // counterintuitive. + UINT state; + if (arrow_direction == RIGHT_POINTING_ARROW) + state = DFCS_MENUARROW; + else + state = DFCS_MENUARROWRIGHT; + return PaintFrameControl(hdc, rect, DFC_MENU, state, is_highlighted); +} + +HRESULT NativeTheme::PaintMenuBackground(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect) const { + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) { + HRESULT result = draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + FrameRect(hdc, rect, GetSysColorBrush(COLOR_3DSHADOW)); + return result; + } + + FillRect(hdc, rect, GetSysColorBrush(COLOR_MENU)); + DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT); + return S_OK; +} + +HRESULT NativeTheme::PaintMenuCheckBackground(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect) const { + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + // Nothing to do for background. + return S_OK; +} + +HRESULT NativeTheme::PaintMenuCheck(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + bool is_highlighted) const { + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) { + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + } + return PaintFrameControl(hdc, rect, DFC_MENU, DFCS_MENUCHECK, is_highlighted); +} + +HRESULT NativeTheme::PaintMenuGutter(HDC hdc, + int part_id, + int state_id, + RECT* rect) const { + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + return E_NOTIMPL; +} + +HRESULT NativeTheme::PaintMenuSeparator(HDC hdc, + int part_id, + int state_id, + RECT* rect) const { + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + DrawEdge(hdc, rect, EDGE_ETCHED, BF_TOP); + return S_OK; +} + +HRESULT NativeTheme::PaintMenuItemBackground(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + bool selected, + RECT* rect) const { + HANDLE handle = GetThemeHandle(MENU); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + if (selected) + FillRect(hdc, rect, GetSysColorBrush(COLOR_HIGHLIGHT)); + return S_OK; +} + +HRESULT NativeTheme::GetThemePartSize(ThemeName theme_name, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + int ts, + SIZE* size) const { + HANDLE handle = GetThemeHandle(theme_name); + if (handle && get_theme_part_size_) + return get_theme_part_size_(handle, hdc, part_id, state_id, rect, ts, size); + + return E_NOTIMPL; +} + +HRESULT NativeTheme::GetThemeColor(ThemeName theme, + int part_id, + int state_id, + int prop_id, + SkColor* color) const { + HANDLE handle = GetThemeHandle(theme); + if (handle && get_theme_color_) { + COLORREF color_ref; + if (get_theme_color_(handle, part_id, state_id, prop_id, &color_ref) == + S_OK) { + *color = gfx::COLORREFToSkColor(color_ref); + return S_OK; + } + } + return E_NOTIMPL; +} + +SkColor NativeTheme::GetThemeColorWithDefault(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int default_sys_color) const { + SkColor color; + if (GetThemeColor(theme, part_id, state_id, prop_id, &color) != S_OK) + color = gfx::COLORREFToSkColor(GetSysColor(default_sys_color)); + return color; +} + +HRESULT NativeTheme::GetThemeInt(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int *value) const { + HANDLE handle = GetThemeHandle(theme); + if (handle && get_theme_int_) + return get_theme_int_(handle, part_id, state_id, prop_id, value); + return E_NOTIMPL; +} + +Size NativeTheme::GetThemeBorderSize(ThemeName theme) const { + // For simplicity use the wildcard state==0, part==0, since it works + // for the cases we currently depend on. + int border; + if (GetThemeInt(theme, 0, 0, TMT_BORDERSIZE, &border) == S_OK) + return Size(border, border); + else + return Size(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE)); +} + + +void NativeTheme::DisableTheming() const { + if (!set_theme_properties_) + return; + set_theme_properties_(0); +} + +HRESULT NativeTheme::PaintFrameControl(HDC hdc, + RECT* rect, + UINT type, + UINT state, + bool is_highlighted) const { + const int width = rect->right - rect->left; + const int height = rect->bottom - rect->top; + + // DrawFrameControl for menu arrow/check wants a monochrome bitmap. + ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL)); + + if (mask_bitmap == NULL) + return E_OUTOFMEMORY; + + ScopedHDC bitmap_dc(CreateCompatibleDC(NULL)); + HGDIOBJ org_bitmap = SelectObject(bitmap_dc, mask_bitmap); + RECT local_rect = { 0, 0, width, height }; + DrawFrameControl(bitmap_dc, &local_rect, type, state); + + // We're going to use BitBlt with a b&w mask. This results in using the dest + // dc's text color for the black bits in the mask, and the dest dc's + // background color for the white bits in the mask. DrawFrameControl draws the + // check in black, and the background in white. + COLORREF old_bg_color = + SetBkColor(hdc, + GetSysColor(is_highlighted ? COLOR_HIGHLIGHT : COLOR_MENU)); + COLORREF old_text_color = + SetTextColor(hdc, + GetSysColor(is_highlighted ? COLOR_HIGHLIGHTTEXT : + COLOR_MENUTEXT)); + BitBlt(hdc, rect->left, rect->top, width, height, bitmap_dc, 0, 0, SRCCOPY); + SetBkColor(hdc, old_bg_color); + SetTextColor(hdc, old_text_color); + + SelectObject(bitmap_dc, org_bitmap); + + return S_OK; +} + +void NativeTheme::CloseHandles() const +{ + if (!close_theme_) + return; + + for (int i = 0; i < LAST; ++i) { + if (theme_handles_[i]) + close_theme_(theme_handles_[i]); + theme_handles_[i] = NULL; + } +} + +HANDLE NativeTheme::GetThemeHandle(ThemeName theme_name) const +{ + if (!open_theme_ || theme_name < 0 || theme_name >= LAST) + return 0; + + if (theme_handles_[theme_name]) + return theme_handles_[theme_name]; + + // Not found, try to load it. + HANDLE handle = 0; + switch (theme_name) { + case NativeTheme::BUTTON: + handle = open_theme_(NULL, L"Button"); + break; + case NativeTheme::TEXTFIELD: + handle = open_theme_(NULL, L"Edit"); + break; + case NativeTheme::MENULIST: + handle = open_theme_(NULL, L"Combobox"); + break; + case NativeTheme::SCROLLBAR: + handle = open_theme_(NULL, L"Scrollbar"); + break; + case NativeTheme::STATUS: + handle = open_theme_(NULL, L"Status"); + break; + case NativeTheme::MENU: + handle = open_theme_(NULL, L"Menu"); + break; + case NativeTheme::WINDOW: + handle = open_theme_(NULL, L"Window"); + break; + case NativeTheme::TAB: + handle = open_theme_(NULL, L"Tab"); + break; + case NativeTheme::LIST: + handle = open_theme_(NULL, L"Listview"); + break; + default: + NOTREACHED(); + } + theme_handles_[theme_name] = handle; + return handle; +} + +} // namespace gfx diff --git a/base/gfx/native_theme.h b/base/gfx/native_theme.h new file mode 100644 index 0000000..918a282 --- /dev/null +++ b/base/gfx/native_theme.h @@ -0,0 +1,310 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// A wrapper class for working with custom XP/Vista themes provided in +// uxtheme.dll. This is a singleton class that can be grabbed using +// NativeTheme::instance(). +// For more information on visual style parts and states, see: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/userex/topics/partsandstates.asp + +#ifndef BASE_GFX_NATIVE_THEME_H__ +#define BASE_GFX_NATIVE_THEME_H__ + +#include <windows.h> +#include <uxtheme.h> +#include "base/basictypes.h" +#include "base/gfx/size.h" +#include "skia/include/SkColor.h" + +namespace gfx { +class PlatformCanvas; + +// TODO: Define class member enums to replace part_id and state_id parameters +// that are currently defined in <vssym32.h>. Afterward, classic_state should +// be removed and class users wouldn't need to include <vssym32.h> anymore. +// This would enable HOT state on non-themed UI (like when RDP'ing) and would +// simplify usage. +// TODO: This class should probably be changed to be platform independent at +// the same time. +class NativeTheme { + public: + enum ThemeName { + BUTTON, + TEXTFIELD, + MENULIST, + SCROLLBAR, + STATUS, + MENU, + WINDOW, + TAB, + LIST, + LAST + }; + + // This enumeration is used within PaintMenuArrow in order to indicate the + // direction the menu arrow should point to. + enum MenuArrowDirection { + LEFT_POINTING_ARROW, + RIGHT_POINTING_ARROW + }; + + typedef HRESULT (WINAPI* DrawThemeBackgroundPtr)(HANDLE theme, + HDC hdc, + int part_id, + int state_id, + const RECT* rect, + const RECT* clip_rect); + typedef HRESULT (WINAPI* DrawThemeBackgroundExPtr)(HANDLE theme, + HDC hdc, + int part_id, + int state_id, + const RECT* rect, + const DTBGOPTS* opts); + typedef HRESULT (WINAPI* GetThemeColorPtr)(HANDLE hTheme, + int part_id, + int state_id, + int prop_id, + COLORREF* color); + typedef HRESULT (WINAPI* GetThemeContentRectPtr)(HANDLE hTheme, + HDC hdc, + int part_id, + int state_id, + const RECT* rect, + RECT* content_rect); + typedef HRESULT (WINAPI* GetThemePartSizePtr)(HANDLE hTheme, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + int ts, + SIZE* size); + typedef HANDLE (WINAPI* OpenThemeDataPtr)(HWND window, + LPCWSTR class_list); + typedef HRESULT (WINAPI* CloseThemeDataPtr)(HANDLE theme); + + typedef void (WINAPI* SetThemeAppPropertiesPtr) (DWORD flags); + typedef BOOL (WINAPI* IsThemeActivePtr)(); + typedef HRESULT (WINAPI* GetThemeIntPtr)(HANDLE hTheme, + int part_id, + int state_id, + int prop_id, + int *value); + + HRESULT PaintButton(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const; + + HRESULT PaintTextField(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect, + COLORREF color, + bool fill_content_area, + bool draw_edges) const; + + HRESULT PaintMenuList(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const; + + // Paints a scrollbar arrow. |classic_state| should have the appropriate + // classic part number ORed in already. + HRESULT PaintScrollbarArrow(HDC hdc, + int state_id, + int classic_state, + RECT* rect) const; + + // Paints a scrollbar track section. |align_rect| is only used in classic + // mode, and makes sure the checkerboard pattern in |target_rect| is aligned + // with one presumed to be in |align_rect|. + HRESULT PaintScrollbarTrack(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* target_rect, + RECT* align_rect, + PlatformCanvas* canvas) const; + + // |arrow_direction| determines whether the arrow is pointing to the left or + // to the right. In RTL locales, sub-menus open from right to left and + // therefore the menu arrow should point to the left and not to the right. + HRESULT PaintMenuArrow(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + MenuArrowDirection arrow_direction, + bool is_highlighted) const; + + HRESULT PaintMenuBackground(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect) const; + + HRESULT PaintMenuCheck(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + bool is_highlighted) const; + + HRESULT PaintMenuCheckBackground(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect) const; + + HRESULT PaintMenuGutter(HDC hdc, + int part_id, + int state_id, + RECT* rect) const; + + HRESULT PaintMenuSeparator(HDC hdc, + int part_id, + int state_id, + RECT* rect) const; + + HRESULT PaintMenuItemBackground(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + bool selected, + RECT* rect) const; + + // Paints a scrollbar thumb or gripper. + HRESULT PaintScrollbarThumb(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const; + + HRESULT PaintStatusGripper(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const; + + HRESULT PaintDialogBackground(HDC dc, bool active, RECT* rect) const; + + HRESULT PaintTabPanelBackground(HDC dc, RECT* rect) const; + + HRESULT PaintListBackground(HDC dc, bool enabled, RECT* rect) const; + + bool IsThemingActive() const; + + HRESULT GetThemePartSize(ThemeName themeName, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + int ts, + SIZE* size) const; + + HRESULT GetThemeColor(ThemeName theme, + int part_id, + int state_id, + int prop_id, + SkColor* color) const; + + // Get the theme color if theming is enabled. If theming is unsupported + // for this part, use Win32's GetSysColor to find the color specified + // by default_sys_color. + SkColor GetThemeColorWithDefault(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int default_sys_color) const; + + HRESULT GetThemeInt(ThemeName theme, + int part_id, + int state_id, + int prop_id, + int *result) const; + + // Get the thickness of the border associated with the specified theme, + // defaulting to GetSystemMetrics edge size if themes are disabled. + // In Classic Windows, borders are typically 2px; on XP+, they are 1px. + Size GetThemeBorderSize(ThemeName theme) const; + + // Disables all theming for top-level windows in the entire process, from + // when this method is called until the process exits. All the other + // methods in this class will continue to work, but their output will ignore + // the user's theme. This is meant for use when running tests that require + // consistent visual results. + void DisableTheming() const; + + // Closes cached theme handles so we can unload the DLL or update our UI + // for a theme change. + void CloseHandles() const; + + // Gets our singleton instance. + static const NativeTheme* instance(); + + private: + NativeTheme(); + ~NativeTheme(); + + HRESULT PaintFrameControl(HDC hdc, + RECT* rect, + UINT type, + UINT state, + bool is_highlighted) const; + + // Returns a handle to the theme data. + HANDLE GetThemeHandle(ThemeName theme_name) const; + + // Function pointers into uxtheme.dll. + DrawThemeBackgroundPtr draw_theme_; + DrawThemeBackgroundExPtr draw_theme_ex_; + GetThemeColorPtr get_theme_color_; + GetThemeContentRectPtr get_theme_content_rect_; + GetThemePartSizePtr get_theme_part_size_; + OpenThemeDataPtr open_theme_; + CloseThemeDataPtr close_theme_; + SetThemeAppPropertiesPtr set_theme_properties_; + IsThemeActivePtr is_theme_active_; + GetThemeIntPtr get_theme_int_; + + // Handle to uxtheme.dll. + HMODULE theme_dll_; + + // A cache of open theme handles. + mutable HANDLE theme_handles_[LAST]; + + DISALLOW_EVIL_CONSTRUCTORS(NativeTheme); +}; + +} // namespace gfx + +#endif // BASE_GFX_NATIVE_THEME_H__ diff --git a/base/gfx/native_theme_unittest.cc b/base/gfx/native_theme_unittest.cc new file mode 100644 index 0000000..6ed50e4 --- /dev/null +++ b/base/gfx/native_theme_unittest.cc @@ -0,0 +1,36 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/native_theme.h" + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(NativeThemeTest, Init) { + ASSERT_TRUE(gfx::NativeTheme::instance() != NULL); +} diff --git a/base/gfx/platform_canvas.cc b/base/gfx/platform_canvas.cc new file mode 100644 index 0000000..a421069 --- /dev/null +++ b/base/gfx/platform_canvas.cc @@ -0,0 +1,105 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/platform_canvas.h" + +#include "base/gfx/bitmap_platform_device.h" +#include "base/logging.h" + +namespace gfx { + +PlatformCanvas::PlatformCanvas() : SkCanvas() { +} + +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque) + : SkCanvas() { + initialize(width, height, is_opaque, NULL); +} + +PlatformCanvas::PlatformCanvas(int width, + int height, + bool is_opaque, + HANDLE shared_section) + : SkCanvas() { + initialize(width, height, is_opaque, shared_section); +} + +PlatformCanvas::~PlatformCanvas() { +} + +void PlatformCanvas::initialize(int width, + int height, + bool is_opaque, + HANDLE shared_section) { + SkDevice* device = + createPlatformDevice(width, height, is_opaque, shared_section); + setDevice(device); + device->unref(); // was created with refcount 1, and setDevice also refs +} + +HDC PlatformCanvas::beginPlatformPaint() { + return getTopPlatformDevice().getBitmapDC(); +} + +void PlatformCanvas::endPlatformPaint() { + // we don't clear the DC here since it will be likely to be used again + // flushing will be done in onAccessBitmap +} + +PlatformDevice& PlatformCanvas::getTopPlatformDevice() const { + // All of our devices should be our special PlatformDevice. + SkCanvas::LayerIter iter(const_cast<PlatformCanvas*>(this), false); + return *static_cast<PlatformDevice*>(iter.device()); +} + +SkDevice* PlatformCanvas::createDevice(SkBitmap::Config config, + int width, + int height, + bool is_opaque, bool isForLayer) { + DCHECK(config == SkBitmap::kARGB_8888_Config); + return createPlatformDevice(width, height, is_opaque, NULL); +} + +SkDevice* PlatformCanvas::createPlatformDevice(int width, + int height, + bool is_opaque, + HANDLE shared_section) { + HDC screen_dc = GetDC(NULL); + SkDevice* device = BitmapPlatformDevice::create(screen_dc, width, height, + is_opaque, shared_section); + ReleaseDC(NULL, screen_dc); + return device; +} + +SkDevice* PlatformCanvas::setBitmapDevice(const SkBitmap&) { + NOTREACHED(); + return NULL; +} + +} // namespace gfx
\ No newline at end of file diff --git a/base/gfx/platform_canvas.h b/base/gfx/platform_canvas.h new file mode 100644 index 0000000..c024a2f --- /dev/null +++ b/base/gfx/platform_canvas.h @@ -0,0 +1,209 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_PLATFORM_CANVAS_H__ +#define BASE_GFX_PLATFORM_CANVAS_H__ + +#include "base/gfx/platform_device.h" +#include "base/basictypes.h" + +#include "SkCanvas.h" + +namespace gfx { + +// This class is a specialization of the regular SkCanvas that is designed to +// work with a gfx::PlatformDevice to manage platform-specific drawing. It +// allows using both Skia operations and platform-specific operations. +class PlatformCanvas : public SkCanvas { + public: + // Set is_opaque if you are going to erase the bitmap and not use + // tranparency: this will enable some optimizations. The shared_section + // parameter is passed to gfx::PlatformDevice::create. See it for details. + // + // If you use the version with no arguments, you MUST call initialize() + PlatformCanvas(); + PlatformCanvas(int width, int height, bool is_opaque); + PlatformCanvas(int width, int height, bool is_opaque, HANDLE shared_section); + virtual ~PlatformCanvas(); + + // For two-part init, call if you use the no-argument constructor above + void initialize(int width, int height, bool is_opaque, HANDLE shared_section); + + // These calls should surround calls to platform drawing routines, the DC + // returned by beginPlatformPaint is the DC that can be used to draw into. + // Call endPlatformPaint when you are done and want to use Skia operations + // again; this will synchronize the bitmap to Windows. + virtual HDC beginPlatformPaint(); + virtual void endPlatformPaint(); + + // Returns the platform device pointer of the topmost rect with a non-empty + // clip. In practice, this is usually either the top layer or nothing, since + // we usually set the clip to new layers when we make them. + // + // If there is no layer that is not all clipped out, this will return a + // dummy device so callers do not have to check. If you are concerned about + // performance, check the clip before doing any painting. + // + // This is different than SkCanvas' getDevice, because that returns the + // bottommost device. + // + // Danger: the resulting device should not be saved. It will be invalidated + // by the next call to save() or restore(). + PlatformDevice& getTopPlatformDevice() const; + + protected: + // Creates a device store for use by the canvas. We override this so that + // the device is always our own so we know that we can use GDI operations + // on it. Simply calls into createPlatformDevice(). + virtual SkDevice* createDevice(SkBitmap::Config, int width, int height, + bool is_opaque, bool isForLayer); + + // Creates a device store for use by the canvas. By default, it creates a + // BitmapPlatformDevice object. Can be overridden to change the object type. + virtual SkDevice* createPlatformDevice(int width, int height, bool is_opaque, + HANDLE shared_section); + + private: + // Unimplemented. + virtual SkDevice* setBitmapDevice(const SkBitmap& bitmap); + + DISALLOW_EVIL_CONSTRUCTORS(PlatformCanvas); +}; + +// A class designed to help with WM_PAINT operations on Windows. It will +// do BeginPaint/EndPaint on init/destruction, and will create the bitmap and +// canvas with the correct size and transform for the dirty rect. The bitmap +// will be automatically painted to the screen on destruction. +// +// You MUST call isEmpty before painting to determine if anything needs +// painting. Sometimes the dirty rect can actually be empty, and this makes +// the bitmap functions we call unhappy. The caller should not paint in this +// case. +// +// Therefore, all you need to do is: +// case WM_PAINT: { +// gfx::PlatformCanvasPaint canvas(hwnd); +// if (!canvas.isEmpty()) { +// ... paint to the canvas ... +// } +// return 0; +// } +template <class T> +class CanvasPaintT : public T { + public: + CanvasPaintT(HWND hwnd) : hwnd_(hwnd), for_paint_(true) { + initPaint(true); + } + + CanvasPaintT(HWND hwnd, bool opaque) : hwnd_(hwnd), for_paint_(true) { + initPaint(opaque); + } + + // Creates a CanvasPaintT for the specified region that paints to the + // specified dc. This does NOT do BeginPaint/EndPaint. + CanvasPaintT(HDC dc, bool opaque, int x, int y, int w, int h) + : hwnd_(NULL), + paint_dc_(dc), + for_paint_(false) { + memset(&ps_, 0, sizeof(ps_)); + ps_.rcPaint.left = x; + ps_.rcPaint.right = x + w; + ps_.rcPaint.top = y; + ps_.rcPaint.bottom = y + h; + init(opaque); + } + + + virtual ~CanvasPaintT() { + if (!isEmpty()) { + restoreToCount(1); + // Commit the drawing to the screen + getTopPlatformDevice().drawToHDC(paint_dc_, + ps_.rcPaint.left, ps_.rcPaint.top, + NULL); + } + if (for_paint_) + EndPaint(hwnd_, &ps_); + } + + // Returns true if the invalid region is empty. The caller should call this + // function to determine if anything needs painting. + bool isEmpty() const { + return ps_.rcPaint.right - ps_.rcPaint.left == 0 || + ps_.rcPaint.bottom - ps_.rcPaint.top == 0; + } + + // Use to access the Windows painting parameters, especially useful for + // getting the bounding rect for painting: paintstruct().rcPaint + const PAINTSTRUCT& paintStruct() const { + return ps_; + } + + // Returns the DC that will be painted to + HDC paintDC() const { + return paint_dc_; + } + + protected: + HWND hwnd_; + HDC paint_dc_; + PAINTSTRUCT ps_; + + private: + void initPaint(bool opaque) { + paint_dc_ = BeginPaint(hwnd_, &ps_); + + init(opaque); + } + + void init(bool opaque) { + // FIXME(brettw) for ClearType, we probably want to expand the bounds of + // painting by one pixel so that the boundaries will be correct (ClearType + // text can depend on the adjacent pixel). Then we would paint just the inset + // pixels to the screen. + initialize(ps_.rcPaint.right - ps_.rcPaint.left, + ps_.rcPaint.bottom - ps_.rcPaint.top, opaque, NULL); + + // This will bring the canvas into the screen coordinate system for the + // dirty rect + translate(SkIntToScalar(-ps_.rcPaint.left), + SkIntToScalar(-ps_.rcPaint.top)); + } + + // If true, this canvas was created for a BeginPaint. + const bool for_paint_; + + DISALLOW_EVIL_CONSTRUCTORS(CanvasPaintT); +}; + +typedef CanvasPaintT<PlatformCanvas> PlatformCanvasPaint; + +} // namespace gfx + +#endif // BASE_GFX_PLATFORM_CANVAS_H__ diff --git a/base/gfx/platform_canvas_unittest.cc b/base/gfx/platform_canvas_unittest.cc new file mode 100644 index 0000000..2d127a7 --- /dev/null +++ b/base/gfx/platform_canvas_unittest.cc @@ -0,0 +1,293 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/gfx/platform_canvas.h" +#include "base/gfx/platform_device.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "SkColor.h" + +namespace gfx { + +namespace { + +// Return true if the canvas is filled to canvas_color, +// and contains a single rectangle filled to rect_color. +bool VerifyRect(const PlatformCanvas& canvas, + uint32_t canvas_color, uint32_t rect_color, + int x, int y, int w, int h) { + PlatformDevice& device = canvas.getTopPlatformDevice(); + const SkBitmap& bitmap = device.accessBitmap(false); + SkAutoLockPixels lock(bitmap); + + for (int cur_y = 0; cur_y < bitmap.height(); cur_y++) { + for (int cur_x = 0; cur_x < bitmap.width(); cur_x++) { + if (cur_x >= x && cur_x < x + w && + cur_y >= y && cur_y < y + h) { + // Inside the square should be rect_color + if (*bitmap.getAddr32(cur_x, cur_y) != rect_color) + return false; + } else { + // Outside the square should be canvas_color + if (*bitmap.getAddr32(cur_x, cur_y) != canvas_color) + return false; + } + } + } + return true; +} + +// Checks whether there is a white canvas with a black square at the given +// location in pixels (not in the canvas coordinate system). +// TODO(ericroman): rename Square to Rect +bool VerifyBlackSquare(const PlatformCanvas& canvas, int x, int y, int w, int h) { + return VerifyRect(canvas, SK_ColorWHITE, SK_ColorBLACK, x, y, w, h); +} + +// Check that every pixel in the canvas is a single color. +bool VerifyCanvasColor(const PlatformCanvas& canvas, uint32_t canvas_color) { + return VerifyRect(canvas, canvas_color, 0, 0, 0, 0, 0); +} + +void DrawGDIRect(PlatformCanvas& canvas, int x, int y, int w, int h) { + HDC dc = canvas.beginPlatformPaint(); + + RECT inner_rc; + inner_rc.left = x; + inner_rc.top = y; + inner_rc.right = x + w; + inner_rc.bottom = y + h; + FillRect(dc, &inner_rc, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH))); + + canvas.endPlatformPaint(); +} + +// Clips the contents of the canvas to the given rectangle. This will be +// intersected with any existing clip. +void AddClip(PlatformCanvas& canvas, int x, int y, int w, int h) { + SkRect rect; + rect.set(SkIntToScalar(x), SkIntToScalar(y), + SkIntToScalar(x + w), SkIntToScalar(y + h)); + canvas.clipRect(rect); +} + +class LayerSaver { + public: + LayerSaver(PlatformCanvas& canvas, int x, int y, int w, int h) + : canvas_(canvas), + x_(x), + y_(y), + w_(w), + h_(h) { + SkRect bounds; + bounds.set(SkIntToScalar(x_), SkIntToScalar(y_), + SkIntToScalar(right()), SkIntToScalar(bottom())); + canvas_.saveLayer(&bounds, NULL); + } + + ~LayerSaver() { + canvas_.getTopPlatformDevice().fixupAlphaBeforeCompositing(); + canvas_.restore(); + } + + int x() const { return x_; } + int y() const { return y_; } + int w() const { return w_; } + int h() const { return h_; } + + // Returns the EXCLUSIVE far bounds of the layer. + int right() const { return x_ + w_; } + int bottom() const { return y_ + h_; } + + private: + PlatformCanvas& canvas_; + int x_, y_, w_, h_; +}; + +// Size used for making layers in many of the below tests. +const int kLayerX = 2; +const int kLayerY = 3; +const int kLayerW = 9; +const int kLayerH = 7; + +// Size used by some tests to draw a rectangle inside the layer. +const int kInnerX = 4; +const int kInnerY = 5; +const int kInnerW = 2; +const int kInnerH = 3; + +} + +// This just checks that our checking code is working properly, it just uses +// regular skia primitives. +TEST(PlatformCanvas, SkLayer) { + // Create the canvas initialized to opaque white. + PlatformCanvas canvas(16, 16, true); + canvas.drawColor(SK_ColorWHITE); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas.drawColor(SK_ColorBLACK); + } + EXPECT_TRUE(VerifyBlackSquare(canvas, kLayerX, kLayerY, kLayerW, kLayerH)); +} + +// Test the GDI clipping. +TEST(PlatformCanvas, GDIClipRegion) { + // Initialize a white canvas + PlatformCanvas canvas(16, 16, true); + canvas.drawColor(SK_ColorWHITE); + EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE)); + + // Test that initially the canvas has no clip region, by filling it + // with a black rectangle. + // Note: Don't use LayerSaver, since internally it sets a clip region. + DrawGDIRect(canvas, 0, 0, 16, 16); + canvas.getTopPlatformDevice().fixupAlphaBeforeCompositing(); + EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorBLACK)); + + // Test that intersecting disjoint clip rectangles sets an empty clip region + canvas.drawColor(SK_ColorWHITE); + EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE)); + { + LayerSaver layer(canvas, 0, 0, 16, 16); + AddClip(canvas, 2, 3, 4, 5); + AddClip(canvas, 4, 9, 10, 10); + DrawGDIRect(canvas, 0, 0, 16, 16); + } + EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE)); +} + +// Test the layers get filled properly by GDI. +TEST(PlatformCanvas, GDILayer) { + // Create the canvas initialized to opaque white. + PlatformCanvas canvas(16, 16, true); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + canvas.drawColor(SK_ColorWHITE); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawGDIRect(canvas, 0, 0, 100, 100); + } + EXPECT_TRUE(VerifyBlackSquare(canvas, kLayerX, kLayerY, kLayerW, kLayerH)); + + // Make a layer and fill it partially to make sure the translation is correct. + canvas.drawColor(SK_ColorWHITE); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawGDIRect(canvas, kInnerX, kInnerY, kInnerW, kInnerH); + } + EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH)); + + // Add a clip on the layer and fill to make make sure clip is correct. + canvas.drawColor(SK_ColorWHITE); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas.save(); + AddClip(canvas, kInnerX, kInnerY, kInnerW, kInnerH); + DrawGDIRect(canvas, 0, 0, 100, 100); + canvas.restore(); + } + EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH)); + + // Add a clip and then make the layer to make sure the clip is correct. + canvas.drawColor(SK_ColorWHITE); + canvas.save(); + AddClip(canvas, kInnerX, kInnerY, kInnerW, kInnerH); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawGDIRect(canvas, 0, 0, 100, 100); + } + canvas.restore(); + EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH)); +} + +// Test that translation + make layer works properly. +TEST(PlatformCanvas, GDITranslateLayer) { + // Create the canvas initialized to opaque white. + PlatformCanvas canvas(16, 16, true); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + canvas.drawColor(SK_ColorWHITE); + canvas.save(); + canvas.translate(1, 1); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawGDIRect(canvas, 0, 0, 100, 100); + } + canvas.restore(); + EXPECT_TRUE(VerifyBlackSquare(canvas, kLayerX + 1, kLayerY + 1, + kLayerW, kLayerH)); + + // Translate then make the layer. + canvas.drawColor(SK_ColorWHITE); + canvas.save(); + canvas.translate(1, 1); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawGDIRect(canvas, kInnerX, kInnerY, kInnerW, kInnerH); + } + canvas.restore(); + EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX + 1, kInnerY + 1, + kInnerW, kInnerH)); + + // Make the layer then translate. + canvas.drawColor(SK_ColorWHITE); + canvas.save(); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas.translate(1, 1); + DrawGDIRect(canvas, kInnerX, kInnerY, kInnerW, kInnerH); + } + canvas.restore(); + EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX + 1, kInnerY + 1, + kInnerW, kInnerH)); + + // Translate both before and after, and have a clip. + canvas.drawColor(SK_ColorWHITE); + canvas.save(); + canvas.translate(1, 1); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas.translate(1, 1); + AddClip(canvas, kInnerX, kInnerY, kInnerW, kInnerH); + DrawGDIRect(canvas, 0, 0, 100, 100); + } + canvas.restore(); + EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX + 2, kInnerY + 2, + kInnerW, kInnerH)); +} + +} // namespace diff --git a/base/gfx/platform_device.cc b/base/gfx/platform_device.cc new file mode 100644 index 0000000..e23ccf3 --- /dev/null +++ b/base/gfx/platform_device.cc @@ -0,0 +1,253 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/platform_device.h" + +#include "base/logging.h" +#include "base/gfx/skia_utils.h" +#include "SkMatrix.h" +#include "SkPath.h" +#include "SkRegion.h" +#include "SkUtils.h" + +namespace gfx { + +PlatformDevice::PlatformDevice(const SkBitmap& bitmap) + : SkDevice(bitmap) { +} + +// static +void PlatformDevice::InitializeDC(HDC context) { + // Enables world transformation. + // If the GM_ADVANCED graphics mode is set, GDI always draws arcs in the + // counterclockwise direction in logical space. This is equivalent to the + // statement that, in the GM_ADVANCED graphics mode, both arc control points + // and arcs themselves fully respect the device context's world-to-device + // transformation. + BOOL res = SetGraphicsMode(context, GM_ADVANCED); + DCHECK_NE(res, 0); + + // Enables dithering. + res = SetStretchBltMode(context, HALFTONE); + DCHECK_NE(res, 0); + // As per SetStretchBltMode() documentation, SetBrushOrgEx() must be called + // right after. + res = SetBrushOrgEx(context, 0, 0, NULL); + DCHECK_NE(res, 0); + + // Sets up default orientation. + res = SetArcDirection(context, AD_CLOCKWISE); + DCHECK_NE(res, 0); + + // Sets up default colors. + res = SetBkColor(context, RGB(255, 255, 255)); + DCHECK_NE(res, CLR_INVALID); + res = SetTextColor(context, RGB(0, 0, 0)); + DCHECK_NE(res, CLR_INVALID); + res = SetDCBrushColor(context, RGB(255, 255, 255)); + DCHECK_NE(res, CLR_INVALID); + res = SetDCPenColor(context, RGB(0, 0, 0)); + DCHECK_NE(res, CLR_INVALID); + + // Sets up default transparency. + res = SetBkMode(context, OPAQUE); + DCHECK_NE(res, 0); + res = SetROP2(context, R2_COPYPEN); + DCHECK_NE(res, 0); +} + +// static +void PlatformDevice::LoadPathToDC(HDC context, const SkPath& path) { + switch (path.getFillType()) { + case SkPath::kWinding_FillType: { + int res = SetPolyFillMode(context, WINDING); + DCHECK(res != 0); + break; + } + case SkPath::kEvenOdd_FillType: { + int res = SetPolyFillMode(context, ALTERNATE); + DCHECK(res != 0); + break; + } + default: { + NOTREACHED(); + break; + } + } + BOOL res = BeginPath(context); + DCHECK(res != 0); + + CubicPaths paths; + if (!SkPathToCubicPaths(&paths, path)) + return; + + std::vector<POINT> points; + for (CubicPaths::const_iterator path(paths.begin()); path != paths.end(); + ++path) { + if (!path->size()) + continue; + // DCHECK_EQ(points.size() % 4, 0); + points.resize(0); + points.reserve(path->size() * 3 / 4 + 1); + points.push_back(SkPointToPOINT(path->front().p[0])); + for (CubicPath::const_iterator point(path->begin()); point != path->end(); + ++point) { + // Never add point->p[0] + points.push_back(SkPointToPOINT(point->p[1])); + points.push_back(SkPointToPOINT(point->p[2])); + points.push_back(SkPointToPOINT(point->p[3])); + } + DCHECK_EQ((points.size() - 1) % 3, 0); + // This is slightly inefficient since all straight line and quadratic lines + // are "upgraded" to a cubic line. + // TODO(maruel): http://b/1147346 We should use + // PolyDraw/PolyBezier/Polyline whenever possible. + res = PolyBezier(context, &points.front(), + static_cast<DWORD>(points.size())); + DCHECK_NE(res, 0); + if (res == 0) + break; + } + if (res == 0) { + // Make sure the path is discarded. + AbortPath(context); + } else { + res = EndPath(context); + DCHECK(res != 0); + } +} + +// static +void PlatformDevice::LoadTransformToDC(HDC dc, const SkMatrix& matrix) { + XFORM xf; + xf.eM11 = matrix[SkMatrix::kMScaleX]; + xf.eM21 = matrix[SkMatrix::kMSkewX]; + xf.eDx = matrix[SkMatrix::kMTransX]; + xf.eM12 = matrix[SkMatrix::kMSkewY]; + xf.eM22 = matrix[SkMatrix::kMScaleY]; + xf.eDy = matrix[SkMatrix::kMTransY]; + SetWorldTransform(dc, &xf); +} + +// static +bool PlatformDevice::SkPathToCubicPaths(CubicPaths* paths, + const SkPath& skpath) { + paths->clear(); + CubicPath* current_path = NULL; + SkPoint current_points[4]; + CubicPoints points_to_add; + SkPath::Iter iter(skpath, false); + for (SkPath::Verb verb = iter.next(current_points); + verb != SkPath::kDone_Verb; + verb = iter.next(current_points)) { + switch (verb) { + case SkPath::kMove_Verb: { // iter.next returns 1 point + // Ignores it since the point is copied in the next operation. See + // SkPath::Iter::next() for reference. + paths->push_back(CubicPath()); + current_path = &paths->back(); + // Skip point addition. + continue; + } + case SkPath::kLine_Verb: { // iter.next returns 2 points + points_to_add.p[0] = current_points[0]; + points_to_add.p[1] = current_points[0]; + points_to_add.p[2] = current_points[1]; + points_to_add.p[3] = current_points[1]; + break; + } + case SkPath::kQuad_Verb: { // iter.next returns 3 points + points_to_add.p[0] = current_points[0]; + points_to_add.p[1] = current_points[1]; + points_to_add.p[2] = current_points[2]; + points_to_add.p[3] = current_points[2]; + break; + } + case SkPath::kCubic_Verb: { // iter.next returns 4 points + points_to_add.p[0] = current_points[0]; + points_to_add.p[1] = current_points[1]; + points_to_add.p[2] = current_points[2]; + points_to_add.p[3] = current_points[3]; + break; + } + case SkPath::kClose_Verb: { // iter.next returns 1 point (the last point) + paths->push_back(CubicPath()); + current_path = &paths->back(); + continue; + } + case SkPath::kDone_Verb: // iter.next returns 0 points + default: { + current_path = NULL; + // Will return false. + break; + } + } + DCHECK(current_path); + if (!current_path) { + paths->clear(); + return false; + } + current_path->push_back(points_to_add); + } + return true; +} + +// static +void PlatformDevice::LoadClippingRegionToDC(HDC context, + const SkRegion& region, + const SkMatrix& transformation) { + HRGN hrgn; + if (region.isEmpty()) { + // region can be empty, in which case everything will be clipped. + hrgn = CreateRectRgn(0, 0, 0, 0); + } else if (region.isRect()) { + // Do the transformation. + SkRect rect; + rect.set(region.getBounds()); + transformation.mapRect(&rect); + SkIRect irect; + rect.round(&irect); + hrgn = CreateRectRgnIndirect(&SkIRectToRECT(irect)); + } else { + // It is complex. + SkPath path; + region.getBoundaryPath(&path); + // Clip. Note that windows clipping regions are not affected by the + // transform so apply it manually. + path.transform(transformation); + LoadPathToDC(context, path); + hrgn = PathToRegion(context); + } + int result = SelectClipRgn(context, hrgn); + DCHECK_NE(result, ERROR); + result = DeleteObject(hrgn); + DCHECK_NE(result, 0); +} + +} // namespace gfx diff --git a/base/gfx/platform_device.h b/base/gfx/platform_device.h new file mode 100644 index 0000000..c3dc998 --- /dev/null +++ b/base/gfx/platform_device.h @@ -0,0 +1,120 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_PLATFORM_DEVICE_H__ +#define BASE_GFX_PLATFORM_DEVICE_H__ + +#include <vector> + +#include "SkDevice.h" + +class SkMatrix; +class SkPath; +class SkRegion; + +namespace gfx { + +// A device is basically a wrapper around SkBitmap that provides a surface for +// SkCanvas to draw into. Our device provides a surface Windows can also write +// to. It also provides functionality to play well with GDI drawing functions. +// This class is abstract and must be subclassed. It provides the basic +// interface to implement it either with or without a bitmap backend. +class PlatformDevice : public SkDevice { + public: + // The DC that corresponds to the bitmap, used for GDI operations drawing + // into the bitmap. This is possibly heavyweight, so it should be existant + // only during one pass of rendering. + virtual HDC getBitmapDC() = 0; + + // Draws to the given screen DC, if the bitmap DC doesn't exist, this will + // temporarily create it. However, if you have created the bitmap DC, it will + // be more efficient if you don't free it until after this call so it doesn't + // have to be created twice. If src_rect is null, then the entirety of the + // source device will be copied. + virtual void drawToHDC(HDC dc, int x, int y, const RECT* src_rect) = 0; + + // Invoke before using GDI functions. See description in platform_device.cc + // for specifics. + // NOTE: x,y,width and height are relative to the current transform. + virtual void prepareForGDI(int x, int y, int width, int height) { } + + // Invoke after using GDI functions. See description in platform_device.cc + // for specifics. + // NOTE: x,y,width and height are relative to the current transform. + virtual void postProcessGDI(int x, int y, int width, int height) { } + + // Sets the opacity of each pixel in the specified region to be opaque. + virtual void makeOpaque(int x, int y, int width, int height) { } + + // Call this function to fix the alpha channels before compositing this layer + // onto another. Internally, the device uses a special alpha method to work + // around problems with Windows. This call will put the values into what + // Skia expects, so it can be composited onto other layers. + // + // After this call, no more drawing can be done because the + // alpha channels will be "correct", which, if this function is called again + // will make them wrong. See the implementation for more discussion. + virtual void fixupAlphaBeforeCompositing() { } + + // Returns if the preferred rendering engine is vectorial or bitmap based. + virtual bool IsVectorial() = 0; + + // Initializes the default settings and colors in a device context. + static void InitializeDC(HDC context); + + // Loads a SkPath into the GDI context. The path can there after be used for + // clipping or as a stroke. + static void LoadPathToDC(HDC context, const SkPath& path); + + // Loads a SkRegion into the GDI context. + static void LoadClippingRegionToDC(HDC context, const SkRegion& region, + const SkMatrix& transformation); + + protected: + // Arrays must be inside structures. + struct CubicPoints { + SkPoint p[4]; + }; + typedef std::vector<CubicPoints> CubicPath; + typedef std::vector<CubicPath> CubicPaths; + + // Forwards |bitmap| to SkDevice's constructor. + PlatformDevice(const SkBitmap& bitmap); + + // Loads the specified Skia transform into the device context, excluding + // perspective (which GDI doesn't support). + static void LoadTransformToDC(HDC dc, const SkMatrix& matrix); + + // Transforms SkPath's paths into a series of cubic path. + static bool SkPathToCubicPaths(CubicPaths* paths, const SkPath& skpath); +}; + +} // namespace gfx + +#endif // BASE_GFX_PLATFORM_DEVICE_H__ diff --git a/base/gfx/png_codec_unittest.cc b/base/gfx/png_codec_unittest.cc new file mode 100644 index 0000000..1b510aa --- /dev/null +++ b/base/gfx/png_codec_unittest.cc @@ -0,0 +1,223 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <math.h> + +#include "base/gfx/png_encoder.h" +#include "base/gfx/png_decoder.h" +#include "testing/gtest/include/gtest/gtest.h" + +static void MakeRGBImage(int w, int h, std::vector<unsigned char>* dat) { + dat->resize(w * h * 3); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + unsigned char* org_px = &(*dat)[(y * w + x) * 3]; + org_px[0] = x * 3; // r + org_px[1] = x * 3 + 1; // g + org_px[2] = x * 3 + 2; // b + } + } +} + +// Set use_transparency to write data into the alpha channel, otherwise it will +// be filled with 0xff. With the alpha channel stripped, this should yield the +// same image as MakeRGBImage above, so the code below can make reference +// images for conversion testing. +static void MakeRGBAImage(int w, int h, bool use_transparency, + std::vector<unsigned char>* dat) { + dat->resize(w * h * 4); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + unsigned char* org_px = &(*dat)[(y * w + x) * 4]; + org_px[0] = x * 3; // r + org_px[1] = x * 3 + 1; // g + org_px[2] = x * 3 + 2; // b + if (use_transparency) + org_px[3] = x*3 + 3; // a + else + org_px[3] = 0xFF; // a (opaque) + } + } +} + +TEST(PNGCodec, EncodeDecodeRGB) { + const int w = 20, h = 20; + + // create an image with known values + std::vector<unsigned char> original; + MakeRGBImage(w, h, &original); + + // encode + std::vector<unsigned char> encoded; + EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_RGB, w, h, + w * 3, false, &encoded)); + + // decode, it should have the same size as the original + std::vector<unsigned char> decoded; + int outw, outh; + EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(), + PNGDecoder::FORMAT_RGB, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be equal + ASSERT_TRUE(original == decoded); +} + +TEST(PNGCodec, EncodeDecodeRGBA) { + const int w = 20, h = 20; + + // create an image with known values, a must be opaque because it will be + // lost during encoding + std::vector<unsigned char> original; + MakeRGBAImage(w, h, true, &original); + + // encode + std::vector<unsigned char> encoded; + EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_RGBA, w, h, + w * 4, false, &encoded)); + + // decode, it should have the same size as the original + std::vector<unsigned char> decoded; + int outw, outh; + EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(), + PNGDecoder::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be exactly equal + ASSERT_TRUE(original == decoded); +} + +// Test that corrupted data decompression causes failures. +TEST(PNGCodec, DecodeCorrupted) { + int w = 20, h = 20; + + // Make some random data (an uncompressed image). + std::vector<unsigned char> original; + MakeRGBImage(w, h, &original); + + // It should fail when given non-JPEG compressed data. + std::vector<unsigned char> output; + int outw, outh; + EXPECT_FALSE(PNGDecoder::Decode(&original[0], original.size(), + PNGDecoder::FORMAT_RGB, &output, + &outw, &outh)); + + // Make some compressed data. + std::vector<unsigned char> compressed; + EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_RGB, w, h, + w * 3, false, &compressed)); + + // Try decompressing a truncated version. + EXPECT_FALSE(PNGDecoder::Decode(&compressed[0], compressed.size() / 2, + PNGDecoder::FORMAT_RGB, &output, + &outw, &outh)); + + // Corrupt it and try decompressing that. + for (int i = 10; i < 30; i++) + compressed[i] = i; + EXPECT_FALSE(PNGDecoder::Decode(&compressed[0], compressed.size(), + PNGDecoder::FORMAT_RGB, &output, + &outw, &outh)); +} + +TEST(PNGCodec, EncodeDecodeBGRA) { + const int w = 20, h = 20; + + // Create an image with known values, alpha must be opaque because it will be + // lost during encoding. + std::vector<unsigned char> original; + MakeRGBAImage(w, h, true, &original); + + // Encode. + std::vector<unsigned char> encoded; + EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_BGRA, w, h, + w * 4, false, &encoded)); + + // Decode, it should have the same size as the original. + std::vector<unsigned char> decoded; + int outw, outh; + EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(), + PNGDecoder::FORMAT_BGRA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be exactly equal. + ASSERT_TRUE(original == decoded); +} + +TEST(PNGCodec, StripAddAlpha) { + const int w = 20, h = 20; + + // These should be the same except one has a 0xff alpha channel. + std::vector<unsigned char> original_rgb; + MakeRGBImage(w, h, &original_rgb); + std::vector<unsigned char> original_rgba; + MakeRGBAImage(w, h, false, &original_rgba); + + // Encode RGBA data as RGB. + std::vector<unsigned char> encoded; + EXPECT_TRUE(PNGEncoder::Encode(&original_rgba[0], PNGEncoder::FORMAT_RGBA, w, h, + w * 4, true, &encoded)); + + // Decode the RGB to RGBA. + std::vector<unsigned char> decoded; + int outw, outh; + EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(), + PNGDecoder::FORMAT_RGBA, &decoded, + &outw, &outh)); + + // Decoded and reference should be the same (opaque alpha). + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original_rgba.size(), decoded.size()); + ASSERT_TRUE(original_rgba == decoded); + + // Encode RGBA to RGBA. + EXPECT_TRUE(PNGEncoder::Encode(&original_rgba[0], PNGEncoder::FORMAT_RGBA, w, h, + w * 4, false, &encoded)); + + // Decode the RGBA to RGB. + EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(), + PNGDecoder::FORMAT_RGB, &decoded, + &outw, &outh)); + + // It should be the same as our non-alpha-channel reference. + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original_rgb.size(), decoded.size()); + ASSERT_TRUE(original_rgb == decoded); +} diff --git a/base/gfx/png_decoder.cc b/base/gfx/png_decoder.cc new file mode 100644 index 0000000..40ba2e8 --- /dev/null +++ b/base/gfx/png_decoder.cc @@ -0,0 +1,376 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/png_decoder.h" + +#include "base/logging.h" +#include "skia/include/SkBitmap.h" + +extern "C" { +#include "png.h" +} + +namespace { + +// Converts BGRA->RGBA and RGBA->BGRA. +void ConvertBetweenBGRAandRGBA(const unsigned char* input, int pixel_width, + unsigned char* output) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &input[x * 4]; + unsigned char* pixel_out = &output[x * 4]; + pixel_out[0] = pixel_in[2]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[0]; + pixel_out[3] = pixel_in[3]; + } +} + +void ConvertRGBAtoRGB(const unsigned char* rgba, int pixel_width, + unsigned char* rgb) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &rgba[x * 4]; + unsigned char* pixel_out = &rgb[x * 3]; + pixel_out[0] = pixel_in[0]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[2]; + } +} + +} // namespace + +// Decoder -------------------------------------------------------------------- +// +// This code is based on WebKit libpng interface (PNGImageDecoder), which is +// in turn based on the Mozilla png decoder. + +namespace { + +// Gamma constants: We assume we're on Windows which uses a gamma of 2.2. +const double kMaxGamma = 21474.83; // Maximum gamma accepted by png library. +const double kDefaultGamma = 2.2; +const double kInverseGamma = 1.0 / kDefaultGamma; + +// Maximum pixel dimension we'll try to decode. +const png_uint_32 kMaxSize = 4096; + +class PngDecoderState { + public: + PngDecoderState(PNGDecoder::ColorFormat ofmt, std::vector<unsigned char>* o) + : output_format(ofmt), + output_channels(0), + output(o), + row_converter(NULL), + width(0), + height(0), + done(false) { + } + + PNGDecoder::ColorFormat output_format; + int output_channels; + + std::vector<unsigned char>* output; + + // Called to convert a row from the library to the correct output format. + // When NULL, no conversion is necessary. + void (*row_converter)(const unsigned char* in, int w, unsigned char* out); + + // Size of the image, set in the info callback. + int width; + int height; + + // Set to true when we've found the end of the data. + bool done; + + private: + DISALLOW_EVIL_CONSTRUCTORS(PngDecoderState); +}; + +void ConvertRGBtoRGBA(const unsigned char* rgb, int pixel_width, + unsigned char* rgba) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &rgb[x * 3]; + unsigned char* pixel_out = &rgba[x * 4]; + pixel_out[0] = pixel_in[0]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[2]; + pixel_out[3] = 0xff; + } +} + +void ConvertRGBtoBGRA(const unsigned char* rgb, int pixel_width, + unsigned char* bgra) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &rgb[x * 3]; + unsigned char* pixel_out = &bgra[x * 4]; + pixel_out[0] = pixel_in[2]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[0]; + pixel_out[3] = 0xff; + } +} + +// Called when the png header has been read. This code is based on the WebKit +// PNGImageDecoder +void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) { + PngDecoderState* state = static_cast<PngDecoderState*>( + png_get_progressive_ptr(png_ptr)); + + int bit_depth, color_type, interlace_type, compression_type; + int filter_type, channels; + png_uint_32 w, h; + png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, + &interlace_type, &compression_type, &filter_type); + + // Bounds check. When the image is unreasonably big, we'll error out and + // end up back at the setjmp call when we set up decoding. + if (w > kMaxSize || h > kMaxSize) + longjmp(png_ptr->jmpbuf, 1); + state->width = static_cast<int>(w); + state->height = static_cast<int>(h); + + // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA. + if (color_type == PNG_COLOR_TYPE_PALETTE || + (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)) + png_set_expand(png_ptr); + + // Transparency for paletted images. + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_expand(png_ptr); + + // Convert 16-bit to 8-bit. + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + // Expand grayscale to RGB. + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + // Deal with gamma and keep it under our control. + double gamma; + if (png_get_gAMA(png_ptr, info_ptr, &gamma)) { + if (gamma <= 0.0 || gamma > kMaxGamma) { + gamma = kInverseGamma; + png_set_gAMA(png_ptr, info_ptr, gamma); + } + png_set_gamma(png_ptr, kDefaultGamma, gamma); + } else { + png_set_gamma(png_ptr, kDefaultGamma, kInverseGamma); + } + + // Tell libpng to send us rows for interlaced pngs. + if (interlace_type == PNG_INTERLACE_ADAM7) + png_set_interlace_handling(png_ptr); + + // Update our info now + png_read_update_info(png_ptr, info_ptr); + channels = png_get_channels(png_ptr, info_ptr); + + // Pick our row format converter necessary for this data. + if (channels == 3) { + switch (state->output_format) { + case PNGDecoder::FORMAT_RGB: + state->row_converter = NULL; // no conversion necessary + state->output_channels = 3; + break; + case PNGDecoder::FORMAT_RGBA: + state->row_converter = &ConvertRGBtoRGBA; + state->output_channels = 4; + break; + case PNGDecoder::FORMAT_BGRA: + state->row_converter = &ConvertRGBtoBGRA; + state->output_channels = 4; + break; + default: + NOTREACHED() << "Unknown output format"; + break; + } + } else if (channels == 4) { + switch (state->output_format) { + case PNGDecoder::FORMAT_RGB: + state->row_converter = &ConvertRGBAtoRGB; + state->output_channels = 3; + break; + case PNGDecoder::FORMAT_RGBA: + state->row_converter = NULL; // no conversion necessary + state->output_channels = 4; + break; + case PNGDecoder::FORMAT_BGRA: + state->row_converter = &ConvertBetweenBGRAandRGBA; + state->output_channels = 4; + break; + default: + NOTREACHED() << "Unknown output format"; + break; + } + } else { + NOTREACHED() << "Unknown input channels"; + longjmp(png_ptr->jmpbuf, 1); + } + + state->output->resize(state->width * state->output_channels * state->height); +} + +void DecodeRowCallback(png_struct* png_ptr, png_byte* new_row, + png_uint_32 row_num, int pass) { + PngDecoderState* state = static_cast<PngDecoderState*>( + png_get_progressive_ptr(png_ptr)); + + DCHECK(pass == 0) << "We didn't turn on interlace handling, but libpng is " + "giving us interlaced data."; + if (static_cast<int>(row_num) > state->height) { + NOTREACHED() << "Invalid row"; + return; + } + + unsigned char* dest = &(*state->output)[ + state->width * state->output_channels * row_num]; + if (state->row_converter) + state->row_converter(new_row, state->width, dest); + else + memcpy(dest, new_row, state->width * state->output_channels); +} + +void DecodeEndCallback(png_struct* png_ptr, png_info* info) { + PngDecoderState* state = static_cast<PngDecoderState*>( + png_get_progressive_ptr(png_ptr)); + + // Mark the image as complete, this will tell the Decode function that we + // have successfully found the end of the data. + state->done = true; +} + +// Automatically destroys the given read structs on destruction to make +// cleanup and error handling code cleaner. +class PngReadStructDestroyer { + public: + PngReadStructDestroyer(png_struct** ps, png_info** pi) : ps_(ps), pi_(pi) { + } + ~PngReadStructDestroyer() { + png_destroy_read_struct(ps_, pi_, NULL); + } + private: + png_struct** ps_; + png_info** pi_; +}; + +} // namespace + +// static +bool PNGDecoder::Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector<unsigned char>* output, + int* w, int* h) { + if (input_size < 8) + return false; // Input data too small to be a png + + // Have libpng check the signature, it likes the first 8 bytes. + if (png_sig_cmp(const_cast<unsigned char*>(input), 0, 8) != 0) + return false; + + png_struct* png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + png_voidp_NULL, + png_error_ptr_NULL, + png_error_ptr_NULL); + if (!png_ptr) + return false; + + png_info* info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return false; + } + + PngReadStructDestroyer destroyer(&png_ptr, &info_ptr); + if (setjmp(png_jmpbuf(png_ptr))) { + // The destroyer will ensure that the structures are cleaned up in this + // case, even though we may get here as a jump from random parts of the + // PNG library called below. + return false; + } + + PngDecoderState state(format, output); + + png_set_progressive_read_fn(png_ptr, &state, &DecodeInfoCallback, + &DecodeRowCallback, &DecodeEndCallback); + png_process_data(png_ptr, info_ptr, const_cast<unsigned char*>(input), input_size); + + if (!state.done) { + // Fed it all the data but the library didn't think we got all the data, so + // this file must be truncated. + output->clear(); + return false; + } + + *w = state.width; + *h = state.height; + return true; +} + +// static +bool PNGDecoder::Decode(const std::vector<unsigned char>* data, + SkBitmap* bitmap) { + DCHECK(bitmap); + if (!data || data->empty()) + return false; + int width, height; + std::vector<unsigned char> decoded_data; + if (PNGDecoder::Decode(&data->front(), data->size(), PNGDecoder::FORMAT_BGRA, + &decoded_data, &width, &height)) { + bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap->allocPixels(); + memcpy(bitmap->getPixels(), &decoded_data.front(), width * height * 4); + return true; + } + return false; +} + +//static +SkBitmap* PNGDecoder::CreateSkBitmapFromBGRAFormat( + std::vector<unsigned char>& bgra, int width, int height) { + SkBitmap* bitmap = new SkBitmap(); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap->allocPixels(); + + bool opaque = false; + unsigned char* bitmap_data = + reinterpret_cast<unsigned char*>(bitmap->getAddr32(0, 0)); + for (int i = width * height * 4 - 4; i >= 0; i -= 4) { + unsigned char alpha = bgra[i + 3]; + if (!opaque && alpha != 255) { + opaque = false; + } + bitmap_data[i + 3] = alpha; + bitmap_data[i] = (bgra[i] * alpha) >> 8; + bitmap_data[i + 1] = (bgra[i + 1] * alpha) >> 8; + bitmap_data[i + 2] = (bgra[i + 2] * alpha) >> 8; + } + + bitmap->setIsOpaque(opaque); + return bitmap; +} diff --git a/base/gfx/png_decoder.h b/base/gfx/png_decoder.h new file mode 100644 index 0000000..4912187 --- /dev/null +++ b/base/gfx/png_decoder.h @@ -0,0 +1,88 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_PNG_DECODER_H__ +#define BASE_GFX_PNG_DECODER_H__ + +#include <vector> + +#include "base/basictypes.h" + +class SkBitmap; + +// Interface for decoding PNG data. This is a wrapper around libpng, +// which has an inconvenient interface for callers. This is currently designed +// for use in tests only (where we control the files), so the handling isn't as +// robust as would be required for a browser (see Decode() for more). WebKit +// has its own more complicated PNG decoder which handles, among other things, +// partially downloaded data. +class PNGDecoder { + public: + enum ColorFormat { + // 3 bytes per pixel (packed), in RGB order regardless of endianness. + // This is the native JPEG format. + FORMAT_RGB, + + // 4 bytes per pixel, in RGBA order in memory regardless of endianness. + FORMAT_RGBA, + + // 4 bytes per pixel, in BGRA order in memory regardless of endianness. + // This is the default Windows DIB order. + FORMAT_BGRA + }; + + // Decodes the PNG data contained in input of length input_size. The + // decoded data will be placed in *output with the dimensions in *w and *h + // on success (returns true). This data will be written in the 'format' + // format. On failure, the values of these output variables are undefined. + // + // This function may not support all PNG types, and it hasn't been tested + // with a large number of images, so assume a new format may not work. It's + // really designed to be able to read in something written by Encode() above. + static bool Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector<unsigned char>* output, + int* w, int* h); + + // A convenience function for decoding PNGs as previously encoded by the PNG + // encoder. Chrome encodes png in the format PNGDecoder::FORMAT_BGRA. + // + // Returns true if data is non-null and can be decoded as a png, false + // otherwise. + static bool Decode(const std::vector<unsigned char>* data, SkBitmap* icon); + + // Create a SkBitmap from a decoded BGRA DIB. The caller owns the returned + // SkBitmap. + static SkBitmap* CreateSkBitmapFromBGRAFormat( + std::vector<unsigned char>& bgra, int width, int height); + + private: + DISALLOW_EVIL_CONSTRUCTORS(PNGDecoder); +}; + +#endif // BASE_GFX_PNG_DECODER_H__ diff --git a/base/gfx/png_encoder.cc b/base/gfx/png_encoder.cc new file mode 100644 index 0000000..ee31a2c --- /dev/null +++ b/base/gfx/png_encoder.cc @@ -0,0 +1,217 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/basictypes.h" +#include "base/gfx/png_encoder.h" +#include "base/logging.h" + +extern "C" { +#include "png.h" +} + +namespace { + +// Converts BGRA->RGBA and RGBA->BGRA. +void ConvertBetweenBGRAandRGBA(const unsigned char* input, int pixel_width, + unsigned char* output) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &input[x * 4]; + unsigned char* pixel_out = &output[x * 4]; + pixel_out[0] = pixel_in[2]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[0]; + pixel_out[3] = pixel_in[3]; + } +} + +void ConvertRGBAtoRGB(const unsigned char* rgba, int pixel_width, + unsigned char* rgb) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &rgba[x * 4]; + unsigned char* pixel_out = &rgb[x * 3]; + pixel_out[0] = pixel_in[0]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[2]; + } +} + +} // namespace + +// Encoder -------------------------------------------------------------------- +// +// This section of the code is based on nsPNGEncoder.cpp in Mozilla +// (Copyright 2005 Google Inc.) + +namespace { + +// Passed around as the io_ptr in the png structs so our callbacks know where +// to write data. +struct PngEncoderState { + PngEncoderState(std::vector<unsigned char>* o) : out(o) {} + std::vector<unsigned char>* out; +}; + +// Called by libpng to flush its internal buffer to ours. +void EncoderWriteCallback(png_structp png, png_bytep data, png_size_t size) { + PngEncoderState* state = static_cast<PngEncoderState*>(png_get_io_ptr(png)); + DCHECK(state->out); + + size_t old_size = state->out->size(); + state->out->resize(old_size + size); + memcpy(&(*state->out)[old_size], data, size); +} + +void ConvertBGRAtoRGB(const unsigned char* bgra, int pixel_width, + unsigned char* rgb) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &bgra[x * 4]; + unsigned char* pixel_out = &rgb[x * 3]; + pixel_out[0] = pixel_in[2]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[0]; + } +} + +// Automatically destroys the given write structs on destruction to make +// cleanup and error handling code cleaner. +class PngWriteStructDestroyer { + public: + PngWriteStructDestroyer(png_struct** ps, png_info** pi) : ps_(ps), pi_(pi) { + } + ~PngWriteStructDestroyer() { + png_destroy_write_struct(ps_, pi_); + } + private: + png_struct** ps_; + png_info** pi_; + + DISALLOW_EVIL_CONSTRUCTORS(PngWriteStructDestroyer); +}; + +} // namespace + +// static +bool PNGEncoder::Encode(const unsigned char* input, ColorFormat format, + int w, int h, int row_byte_width, + bool discard_transparency, + std::vector<unsigned char>* output) { + // Run to convert an input row into the output row format, NULL means no + // conversion is necessary. + void (*converter)(const unsigned char* in, int w, unsigned char* out) = NULL; + + int input_color_components, output_color_components; + int png_output_color_type; + switch (format) { + case FORMAT_RGB: + input_color_components = 3; + output_color_components = 3; + png_output_color_type = PNG_COLOR_TYPE_RGB; + discard_transparency = false; + break; + + case FORMAT_RGBA: + input_color_components = 4; + if (discard_transparency) { + output_color_components = 3; + png_output_color_type = PNG_COLOR_TYPE_RGB; + converter = ConvertRGBAtoRGB; + } else { + output_color_components = 4; + png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA; + converter = NULL; + } + break; + + case FORMAT_BGRA: + input_color_components = 4; + if (discard_transparency) { + output_color_components = 3; + png_output_color_type = PNG_COLOR_TYPE_RGB; + converter = ConvertBGRAtoRGB; + } else { + output_color_components = 4; + png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA; + converter = ConvertBetweenBGRAandRGBA; + } + break; + + default: + NOTREACHED() << "Unknown pixel format"; + return false; + } + + // Row stride should be at least as long as the length of the data. + DCHECK(input_color_components * w <= row_byte_width); + + png_struct* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + png_voidp_NULL, + png_error_ptr_NULL, + png_error_ptr_NULL); + if (!png_ptr) + return false; + png_info* info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return false; + } + PngWriteStructDestroyer destroyer(&png_ptr, &info_ptr); + + if (setjmp(png_jmpbuf(png_ptr))) { + // The destroyer will ensure that the structures are cleaned up in this + // case, even though we may get here as a jump from random parts of the + // PNG library called below. + return false; + } + + // Set our callback for libpng to give us the data. + PngEncoderState state(output); + png_set_write_fn(png_ptr, &state, EncoderWriteCallback, NULL); + + png_set_IHDR(png_ptr, info_ptr, w, h, 8, png_output_color_type, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + png_write_info(png_ptr, info_ptr); + + if (!converter) { + // No conversion needed, give the data directly to libpng. + for (int y = 0; y < h; y ++) + png_write_row(png_ptr, const_cast<unsigned char*>(&input[y * row_byte_width])); + } else { + // Needs conversion using a separate buffer. + unsigned char* row = new unsigned char[w * output_color_components]; + for (int y = 0; y < h; y ++) { + converter(&input[y * row_byte_width], w, row); + png_write_row(png_ptr, row); + } + delete[] row; + } + + png_write_end(png_ptr, info_ptr); + return true; +} diff --git a/base/gfx/png_encoder.h b/base/gfx/png_encoder.h new file mode 100644 index 0000000..3129775 --- /dev/null +++ b/base/gfx/png_encoder.h @@ -0,0 +1,83 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_PNG_ENCODER_H__ +#define BASE_GFX_PNG_ENCODER_H__ + +#include <vector> + +#include "base/basictypes.h" + +// Interface for encoding PNG data. This is a wrapper around libpng, +// which has an inconvenient interface for callers. This is currently designed +// for use in tests only (where we control the files), so the handling isn't as +// robust as would be required for a browser (see Decode() for more). WebKit +// has its own more complicated PNG decoder which handles, among other things, +// partially downloaded data. +class PNGEncoder { + public: + enum ColorFormat { + // 3 bytes per pixel (packed), in RGB order regardless of endianness. + // This is the native JPEG format. + FORMAT_RGB, + + // 4 bytes per pixel, in RGBA order in memory regardless of endianness. + FORMAT_RGBA, + + // 4 bytes per pixel, in BGRA order in memory regardless of endianness. + // This is the default Windows DIB order. + FORMAT_BGRA + }; + + // Encodes the given raw 'input' data, with each pixel being represented as + // given in 'format'. The encoded PNG data will be written into the supplied + // vector and true will be returned on success. On failure (false), the + // contents of the output buffer are undefined. + // + // When writing alpha values, the input colors are assumed to be post + // multiplied. + // + // w, h: dimensions of the image + // row_byte_width: the width in bytes of each row. This may be greater than + // w * bytes_per_pixel if there is extra padding at the end of each row + // (often, each row is padded to the next machine word). + // discard_transparency: when true, and when the input data format includes + // alpha values, these alpha values will be discarded and only RGB will be + // written to the resulting file. Otherwise, alpha values in the input + // will be preserved. + static bool Encode(const unsigned char* input, ColorFormat format, + int w, int h, int row_byte_width, + bool discard_transparency, + std::vector<unsigned char>* output); + + private: + DISALLOW_EVIL_CONSTRUCTORS(PNGEncoder); +}; + +#endif // BASE_GFX_PNG_ENCODER_H__ diff --git a/base/gfx/point.cc b/base/gfx/point.cc new file mode 100644 index 0000000..7c435a0 --- /dev/null +++ b/base/gfx/point.cc @@ -0,0 +1,52 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/point.h" + +#include <windows.h> + +namespace gfx { + +Point::Point() : x_(0), y_(0) { +} + +Point::Point(int x, int y) : x_(x), y_(y) { +} + +Point::Point(const POINT& point) : x_(point.x), y_(point.y) { +} + +POINT Point::ToPOINT() const { + POINT p; + p.x = x_; + p.y = y_; + return p; +} + +} // namespace gfx diff --git a/base/gfx/point.h b/base/gfx/point.h new file mode 100644 index 0000000..3e09a81 --- /dev/null +++ b/base/gfx/point.h @@ -0,0 +1,88 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_POINT_H__ +#define BASE_GFX_POINT_H__ + +#ifdef UNIT_TEST +#include <iostream> +#endif + +typedef struct tagPOINT POINT; + +namespace gfx { + +// +// A point has an x and y coordinate. +// +class Point { + public: + Point(); + Point(int x, int y); + explicit Point(const POINT& point); + + ~Point() {} + + int x() const { return x_; } + int y() const { return y_; } + + void SetPoint(int x, int y) { + x_ = x; + y_ = y; + } + + void set_x(int x) { x_ = x; } + void set_y(int y) { y_ = y; } + + bool operator==(const Point& rhs) const { + return x_ == rhs.x_ && y_ == rhs.y_; + } + + bool operator!=(const Point& rhs) const { + return !(*this == rhs); + } + + POINT ToPOINT() const; + + private: + int x_; + int y_; +}; + +} // namespace gfx + +#ifdef UNIT_TEST + +inline std::ostream& operator<<(std::ostream& out, const gfx::Point& p) { + return out << p.x() << "," << p.y(); +} + +#endif // #ifdef UNIT_TEST + +#endif // BASE_GFX_POINT_H__ diff --git a/base/gfx/rect.cc b/base/gfx/rect.cc new file mode 100644 index 0000000..dda7e67 --- /dev/null +++ b/base/gfx/rect.cc @@ -0,0 +1,217 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/rect.h" + +#include <windows.h> + +#include "base/logging.h" + +namespace { + +void AdjustAlongAxis(int dst_origin, int dst_size, int* origin, int* size) { + if (*origin < dst_origin) { + *origin = dst_origin; + *size = std::min(dst_size, *size); + } else { + *size = std::min(dst_size, *size); + *origin = std::min(dst_origin + dst_size, *origin + *size) - *size; + } +} + +} // namespace + +namespace gfx { + +Rect::Rect() { +} + +Rect::Rect(int width, int height) { + set_width(width); + set_height(height); +} + +Rect::Rect(int x, int y, int width, int height) + : origin_(x, y) { + set_width(width); + set_height(height); +} + +Rect::Rect(const RECT& r) + : origin_(r.left, r.top) { + set_width(r.right - r.left); + set_height(r.bottom - r.top); +} + +Rect& Rect::operator=(const RECT& r) { + origin_.SetPoint(r.left, r.top); + set_width(r.right - r.left); + set_height(r.bottom - r.top); + return *this; +} + +void Rect::set_width(int width) { + if (width < 0) { + NOTREACHED(); + width = 0; + } + + size_.set_width(width); +} +void Rect::set_height(int height) { + if (height < 0) { + NOTREACHED(); + height = 0; + } + + size_.set_height(height); +} + +void Rect::SetRect(int x, int y, int width, int height) { + origin_.SetPoint(x, y); + set_width(width); + set_height(height); +} + +void Rect::Inset(int horizontal, int vertical) { + set_x(x() + horizontal); + set_y(y() + vertical); + set_width(std::max(width() - (horizontal * 2), 0)); + set_height(std::max(height() - (vertical * 2), 0)); +} + +void Rect::Offset(int horizontal, int vertical) { + set_x(x() + horizontal); + set_y(y() + vertical); +} + +bool Rect::IsEmpty() const { + return width() == 0 || height() == 0; +} + +bool Rect::operator==(const Rect& other) const { + return origin_ == other.origin_ && size_ == other.size_; +} + +RECT Rect::ToRECT() const { + RECT r; + r.left = x(); + r.right = right(); + r.top = y(); + r.bottom = bottom(); + return r; +} + +bool Rect::Contains(int point_x, int point_y) const { + return (point_x >= x()) && (point_x < right()) && + (point_y >= y()) && (point_y < bottom()); +} + +bool Rect::Contains(const Rect& rect) const { + return (rect.x() >= x() && rect.right() <= right() && + rect.y() >= y() && rect.bottom() <= bottom()); +} + +bool Rect::Intersects(const Rect& rect) const { + return !(rect.x() >= right() || rect.right() <= x() || + rect.y() >= bottom() || rect.bottom() <= y()); +} + +Rect Rect::Intersect(const Rect& rect) const { + int rx = std::max(x(), rect.x()); + int ry = std::max(y(), rect.y()); + int rr = std::min(right(), rect.right()); + int rb = std::min(bottom(), rect.bottom()); + + if (rx >= rr || ry >= rb) + rx = ry = rr = rb = 0; // non-intersecting + + return Rect(rx, ry, rr - rx, rb - ry); +} + +Rect Rect::Union(const Rect& rect) const { + // special case empty rects... + if (IsEmpty()) + return rect; + if (rect.IsEmpty()) + return *this; + + int rx = std::min(x(), rect.x()); + int ry = std::min(y(), rect.y()); + int rr = std::max(right(), rect.right()); + int rb = std::max(bottom(), rect.bottom()); + + return Rect(rx, ry, rr - rx, rb - ry); +} + +Rect Rect::Subtract(const Rect& rect) const { + // boundary cases: + if (!Intersects(rect)) + return *this; + if (rect.Contains(*this)) + return Rect(); + + int rx = x(); + int ry = y(); + int rr = right(); + int rb = bottom(); + + if (rect.y() <= y() && rect.bottom() >= bottom()) { + // complete intersection in the y-direction + if (rect.x() <= x()) { + rx = rect.right(); + } else { + rr = rect.x(); + } + } else if (rect.x() <= x() && rect.right() >= right()) { + // complete intersection in the x-direction + if (rect.y() <= y()) { + ry = rect.bottom(); + } else { + rb = rect.y(); + } + } + return Rect(rx, ry, rr - rx, rb - ry); +} + +Rect Rect::AdjustToFit(const Rect& rect) const { + int new_x = x(); + int new_y = y(); + int new_width = width(); + int new_height = height(); + AdjustAlongAxis(rect.x(), rect.width(), &new_x, &new_width); + AdjustAlongAxis(rect.y(), rect.height(), &new_y, &new_height); + return Rect(new_x, new_y, new_width, new_height); +} + +Point Rect::CenterPoint() const { + return Point(x() + (width() + 1) / 2, y() + (height() + 1) / 2); +} + +} // namespace gfx diff --git a/base/gfx/rect.h b/base/gfx/rect.h new file mode 100644 index 0000000..486f799 --- /dev/null +++ b/base/gfx/rect.h @@ -0,0 +1,153 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Defines a simple integer rectangle class. The containment semantics +// are array-like; that is, the coordinate (x, y) is considered to be +// contained by the rectangle, but the coordinate (x + width, y) is not. +// The class will happily let you create malformed rectangles (that is, +// rectangles with negative width and/or height), but there will be assertions +// in the operations (such as contain()) to complain in this case. + +#ifndef BASE_GFX_RECT_H__ +#define BASE_GFX_RECT_H__ + +#include "base/gfx/size.h" +#include "base/gfx/point.h" + +typedef struct tagRECT RECT; + +namespace gfx { + +class Rect { + public: + Rect(); + Rect(int width, int height); + Rect(int x, int y, int width, int height); + explicit Rect(const RECT& r); + Rect(const gfx::Point& origin, const gfx::Size& size); + + ~Rect() {} + + Rect& operator=(const RECT& r); + + int x() const { return origin_.x(); } + void set_x(int x) { origin_.set_x(x); } + + int y() const { return origin_.y(); } + void set_y(int y) { origin_.set_y(y); } + + int width() const { return size_.width(); } + void set_width(int width); + + int height() const { return size_.height(); } + void set_height(int height); + + const gfx::Point& origin() const { return origin_; } + void set_origin(const gfx::Point& origin) { origin_ = origin; } + + const gfx::Size& size() const { return size_; } + + int right() const { return x() + width(); } + int bottom() const { return y() + height(); } + + void SetRect(int x, int y, int width, int height); + + // Shrink the rectangle by a horizontal and vertical distance on all sides. + void Inset(int horizontal, int vertical); + + // Move the rectangle by a horizontal and vertical distance. + void Offset(int horizontal, int vertical); + + // Returns true if the area of the rectangle is zero. + bool IsEmpty() const; + + bool operator==(const Rect& other) const; + + bool operator!=(const Rect& other) const { + return !(*this == other); + } + + // Construct an equivalent Win32 RECT object. + RECT ToRECT() const; + + // Returns true if the point identified by point_x and point_y falls inside + // this rectangle. The point (x, y) is inside the rectangle, but the + // point (x + width, y + height) is not. + bool Contains(int point_x, int point_y) const; + + // Returns true if this rectangle contains the specified rectangle. + bool Contains(const Rect& rect) const; + + // Returns true if this rectangle intersects the specified rectangle. + bool Intersects(const Rect& rect) const; + + // Computes the intersection of this rectangle with the given rectangle. + Rect Intersect(const Rect& rect) const; + + // Computes the union of this rectangle with the given rectangle. The union + // is the smallest rectangle containing both rectangles. + Rect Union(const Rect& rect) const; + + // Computes the rectangle resulting from subtracting |rect| from |this|. If + // |rect| does not intersect completely in either the x- or y-direction, then + // |*this| is returned. If |rect| contains |this|, then an empty Rect is + // returned. + Rect Subtract(const Rect& rect) const; + + // Returns true if this rectangle equals that of the supplied rectangle. + bool Equals(const Rect& rect) const { + return *this == rect; + } + + // Fits as much of the receiving rectangle into the supplied rectangle as + // possible, returning the result. For example, if the receiver had + // a x-location of 2 and a width of 4, and the supplied rectangle had + // an x-location of 0 with a width of 5, the returned rectangle would have + // an x-location of 1 with a width of 4. + Rect AdjustToFit(const Rect& rect) const; + + // Returns the center of this rectangle. + Point CenterPoint() const; + + private: + gfx::Point origin_; + gfx::Size size_; +}; + +} // namespace gfx + +#ifdef UNIT_TEST + +inline std::ostream& operator<<(std::ostream& out, const gfx::Rect& r) { + return out << r.origin() << " " << r.size(); +} + +#endif // #ifdef UNIT_TEST + +#endif // BASE_GFX_RECT_H__ diff --git a/base/gfx/rect_unittest.cc b/base/gfx/rect_unittest.cc new file mode 100644 index 0000000..c218cc9 --- /dev/null +++ b/base/gfx/rect_unittest.cc @@ -0,0 +1,295 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/basictypes.h" +#include "base/gfx/rect.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +typedef testing::Test RectTest; + +TEST(RectTest, Contains) { + static const struct ContainsCase { + int rect_x; + int rect_y; + int rect_width; + int rect_height; + int point_x; + int point_y; + bool contained; + } contains_cases[] = { + {0, 0, 10, 10, 0, 0, true}, + {0, 0, 10, 10, 5, 5, true}, + {0, 0, 10, 10, 9, 9, true}, + {0, 0, 10, 10, 5, 10, false}, + {0, 0, 10, 10, 10, 5, false}, + {0, 0, 10, 10, -1, -1, false}, + {0, 0, 10, 10, 50, 50, false}, + #ifdef NDEBUG + {0, 0, -10, -10, 0, 0, false}, + #endif // NDEBUG + }; + for (int i = 0; i < arraysize(contains_cases); ++i) { + const ContainsCase& value = contains_cases[i]; + gfx::Rect rect(value.rect_x, value.rect_y, + value.rect_width, value.rect_height); + EXPECT_EQ(value.contained, rect.Contains(value.point_x, value.point_y)); + } +} + +TEST(RectTest, Intersects) { + static const struct { + int x1; // rect 1 + int y1; + int w1; + int h1; + int x2; // rect 2 + int y2; + int w2; + int h2; + bool intersects; + } tests[] = { + { 0, 0, 0, 0, 0, 0, 0, 0, false }, + { 0, 0, 10, 10, 0, 0, 10, 10, true }, + { 0, 0, 10, 10, 10, 10, 10, 10, false }, + { 10, 10, 10, 10, 0, 0, 10, 10, false }, + { 10, 10, 10, 10, 5, 5, 10, 10, true }, + { 10, 10, 10, 10, 15, 15, 10, 10, true }, + { 10, 10, 10, 10, 20, 15, 10, 10, false }, + { 10, 10, 10, 10, 21, 15, 10, 10, false } + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + EXPECT_EQ(tests[i].intersects, r1.Intersects(r2)); + } +} + +TEST(RectTest, Intersect) { + static const struct { + int x1; // rect 1 + int y1; + int w1; + int h1; + int x2; // rect 2 + int y2; + int w2; + int h2; + int x3; // rect 3: the union of rects 1 and 2 + int y3; + int w3; + int h3; + } tests[] = { + { 0, 0, 0, 0, // zeros + 0, 0, 0, 0, + 0, 0, 0, 0 }, + { 0, 0, 4, 4, // equal + 0, 0, 4, 4, + 0, 0, 4, 4 }, + { 0, 0, 4, 4, // neighboring + 4, 4, 4, 4, + 0, 0, 0, 0 }, + { 0, 0, 4, 4, // overlapping corners + 2, 2, 4, 4, + 2, 2, 2, 2 }, + { 0, 0, 4, 4, // T junction + 3, 1, 4, 2, + 3, 1, 1, 2 }, + { 3, 0, 2, 2, // gap + 0, 0, 2, 2, + 0, 0, 0, 0 } + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + gfx::Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3); + gfx::Rect ir = r1.Intersect(r2); + EXPECT_EQ(r3.x(), ir.x()); + EXPECT_EQ(r3.y(), ir.y()); + EXPECT_EQ(r3.width(), ir.width()); + EXPECT_EQ(r3.height(), ir.height()); + } +} + +TEST(RectTest, Union) { + static const struct { + int x1; // rect 1 + int y1; + int w1; + int h1; + int x2; // rect 2 + int y2; + int w2; + int h2; + int x3; // rect 3: the union of rects 1 and 2 + int y3; + int w3; + int h3; + } tests[] = { + { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 }, + { 0, 0, 4, 4, + 0, 0, 4, 4, + 0, 0, 4, 4 }, + { 0, 0, 4, 4, + 4, 4, 4, 4, + 0, 0, 8, 8 }, + { 0, 0, 4, 4, + 0, 5, 4, 4, + 0, 0, 4, 9 }, + { 0, 0, 2, 2, + 3, 3, 2, 2, + 0, 0, 5, 5 }, + { 3, 3, 2, 2, // reverse r1 and r2 from previous test + 0, 0, 2, 2, + 0, 0, 5, 5 }, + { 0, 0, 0, 0, // union with empty rect + 2, 2, 2, 2, + 2, 2, 2, 2 } + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + gfx::Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3); + gfx::Rect u = r1.Union(r2); + EXPECT_EQ(r3.x(), u.x()); + EXPECT_EQ(r3.y(), u.y()); + EXPECT_EQ(r3.width(), u.width()); + EXPECT_EQ(r3.height(), u.height()); + } +} + +TEST(RectTest, Equals) { + ASSERT_TRUE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 0, 0, 0))); + ASSERT_TRUE(gfx::Rect(1, 2, 3, 4).Equals(gfx::Rect(1, 2, 3, 4))); + ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 0, 0, 1))); + ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 0, 1, 0))); + ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 1, 0, 0))); + ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(1, 0, 0, 0))); +} + +TEST(RectTest, AdjustToFit) { + static const struct { + int x1; // source + int y1; + int w1; + int h1; + int x2; // target + int y2; + int w2; + int h2; + int x3; // rect 3: results of invoking AdjustToFit + int y3; + int w3; + int h3; + } tests[] = { + { 0, 0, 2, 2, + 0, 0, 2, 2, + 0, 0, 2, 2 }, + { 2, 2, 3, 3, + 0, 0, 4, 4, + 1, 1, 3, 3 }, + { -1, -1, 5, 5, + 0, 0, 4, 4, + 0, 0, 4, 4 }, + { 2, 2, 4, 4, + 0, 0, 3, 3, + 0, 0, 3, 3 }, + { 2, 2, 1, 1, + 0, 0, 3, 3, + 2, 2, 1, 1 } + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1); + gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2); + gfx::Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3); + gfx::Rect u(r1.AdjustToFit(r2)); + EXPECT_EQ(r3.x(), u.x()); + EXPECT_EQ(r3.y(), u.y()); + EXPECT_EQ(r3.width(), u.width()); + EXPECT_EQ(r3.height(), u.height()); + } +} + +TEST(RectText, Subtract) { + // Matching + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(10, 10, 20, 20)).Equals( + gfx::Rect(0, 0, 0, 0))); + + // Contains + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(5, 5, 30, 30)).Equals( + gfx::Rect(0, 0, 0, 0))); + + // No intersection + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(30, 30, 20, 20)).Equals( + gfx::Rect(10, 10, 20, 20))); + + // Not a complete intersection in either direction + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(15, 15, 20, 20)).Equals( + gfx::Rect(10, 10, 20, 20))); + + // Complete intersection in the x-direction + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(10, 15, 20, 20)).Equals( + gfx::Rect(10, 10, 20, 5))); + + // Complete intersection in the x-direction + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(5, 15, 30, 20)).Equals( + gfx::Rect(10, 10, 20, 5))); + + // Complete intersection in the x-direction + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(5, 5, 30, 20)).Equals( + gfx::Rect(10, 25, 20, 5))); + + // Complete intersection in the y-direction + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(10, 10, 10, 30)).Equals( + gfx::Rect(20, 10, 10, 20))); + + // Complete intersection in the y-direction + EXPECT_TRUE( + gfx::Rect(10, 10, 20, 20).Subtract( + gfx::Rect(5, 5, 20, 30)).Equals( + gfx::Rect(25, 10, 5, 20))); +} diff --git a/base/gfx/size.cc b/base/gfx/size.cc new file mode 100644 index 0000000..153c29e --- /dev/null +++ b/base/gfx/size.cc @@ -0,0 +1,49 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/size.h" + +#include <windows.h> + +#include <ostream> + +namespace gfx { + +SIZE Size::ToSIZE() const { + SIZE s; + s.cx = width_; + s.cy = height_; + return s; +} + +std::ostream& operator<<(std::ostream& out, const gfx::Size& s) { + return out << s.width() << "x" << s.height(); +} + +} // namespace gfx diff --git a/base/gfx/size.h b/base/gfx/size.h new file mode 100644 index 0000000..cba2e51 --- /dev/null +++ b/base/gfx/size.h @@ -0,0 +1,91 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_SIZE_H__ +#define BASE_GFX_SIZE_H__ + +#ifdef UNIT_TEST +#include <iostream> +#endif + +typedef struct tagSIZE SIZE; + +namespace gfx { + +// +// A size has width and height values. +// +class Size { + public: + Size() : width_(0), height_(0) {} + Size(int width, int height) : width_(width), height_(height) {} + + ~Size() {} + + int width() const { return width_; } + int height() const { return height_; } + + void SetSize(int width, int height) { + width_ = width; + height_ = height; + } + + void set_width(int width) { width_ = width; } + void set_height(int height) { height_ = height; } + + bool operator==(const Size& s) const { + return width_ == s.width_ && height_ == s.height_; + } + + bool operator!=(const Size& s) const { + return !(*this == s); + } + + bool IsEmpty() const { + return !width_ && !height_; + } + + SIZE ToSIZE() const; + + private: + int width_; + int height_; +}; + +} // namespace gfx + +#ifdef UNIT_TEST + +inline std::ostream& operator<<(std::ostream& out, const gfx::Size& s) { + return out << s.width() << "x" << s.height(); +} + +#endif // #ifdef UNIT_TEST + +#endif // BASE_GFX_SIZE_H__ diff --git a/base/gfx/skia_utils.cc b/base/gfx/skia_utils.cc new file mode 100644 index 0000000..60e977d --- /dev/null +++ b/base/gfx/skia_utils.cc @@ -0,0 +1,99 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/skia_utils.h" + +#include "base/logging.h" +#include "SkRect.h" +#include "SkGradientShader.h" + +namespace { + +COMPILE_ASSERT(offsetof(RECT, left) == offsetof(SkIRect, fLeft), o1); +COMPILE_ASSERT(offsetof(RECT, top) == offsetof(SkIRect, fTop), o2); +COMPILE_ASSERT(offsetof(RECT, right) == offsetof(SkIRect, fRight), o3); +COMPILE_ASSERT(offsetof(RECT, bottom) == offsetof(SkIRect, fBottom), o4); +COMPILE_ASSERT(sizeof(RECT().left) == sizeof(SkIRect().fLeft), o5); +COMPILE_ASSERT(sizeof(RECT().top) == sizeof(SkIRect().fTop), o6); +COMPILE_ASSERT(sizeof(RECT().right) == sizeof(SkIRect().fRight), o7); +COMPILE_ASSERT(sizeof(RECT().bottom) == sizeof(SkIRect().fBottom), o8); +COMPILE_ASSERT(sizeof(RECT) == sizeof(SkIRect), o9); + +} // namespace + +namespace gfx { + +POINT SkPointToPOINT(const SkPoint& point) { + POINT win_point = { SkScalarRound(point.fX), SkScalarRound(point.fY) }; + return win_point; +} + +SkRect RECTToSkRect(const RECT& rect) { + SkRect sk_rect = { SkIntToScalar(rect.left), SkIntToScalar(rect.top), + SkIntToScalar(rect.right), SkIntToScalar(rect.bottom) }; + return sk_rect; +} + +SkShader* CreateGradientShader(int start_point, + int end_point, + SkColor start_color, + SkColor end_color) { + SkColor grad_colors[2] = { start_color, end_color}; + SkPoint grad_points[2]; + grad_points[0].set(SkIntToScalar(0), SkIntToScalar(start_point)); + grad_points[1].set(SkIntToScalar(0), SkIntToScalar(end_point)); + + return SkGradientShader::CreateLinear( + grad_points, grad_colors, NULL, 2, SkShader::kRepeat_TileMode); +} + + +SkColor COLORREFToSkColor(COLORREF color) { +#ifndef _MSC_VER + return SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color)); +#else + // ARGB = 0xFF000000 | ((0BGR -> RGB0) >> 8) + return 0xFF000000u | (_byteswap_ulong(color) >> 8); +#endif +} + +COLORREF SkColorToCOLORREF(SkColor color) { + // Currently, Alpha is always 255 or the color is 0 so there is no need to + // demultiply the channels. If this DCHECK() is ever hit, the full + // (SkColorGetX(color) * 255 / a) will have to be added in the conversion. + DCHECK((0xFF == SkColorGetA(color)) || (0 == color)); +#ifndef _MSC_VER + return RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)); +#else + // 0BGR = ((ARGB -> BGRA) >> 8) + return (_byteswap_ulong(color) >> 8); +#endif +} + +} // namespace gfx diff --git a/base/gfx/skia_utils.h b/base/gfx/skia_utils.h new file mode 100644 index 0000000..989a17e --- /dev/null +++ b/base/gfx/skia_utils.h @@ -0,0 +1,80 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_SKIA_UTILS_H__ +#define BASE_GFX_SKIA_UTILS_H__ + +#include "SkColor.h" +#include "SkShader.h" + +struct SkIRect; +struct SkPoint; +struct SkRect; +typedef unsigned long DWORD; +typedef DWORD COLORREF; +typedef struct tagPOINT POINT; +typedef struct tagRECT RECT; + +namespace gfx { + +// Converts a Skia point to a Windows POINT. +POINT SkPointToPOINT(const SkPoint& point); + +// Converts a Windows RECT to a Skia rect. +SkRect RECTToSkRect(const RECT& rect); + +// Converts a Windows RECT to a Skia rect. +// Both use same in-memory format. Verified by COMPILE_ASSERT() in +// skia_utils.cc. +inline const SkIRect& RECTToSkIRect(const RECT& rect) { + return reinterpret_cast<const SkIRect&>(rect); +} + +// Converts a Skia rect to a Windows RECT. +// Both use same in-memory format. Verified by COMPILE_ASSERT() in +// skia_utils.cc. +inline const RECT& SkIRectToRECT(const SkIRect& rect) { + return reinterpret_cast<const RECT&>(rect); +} + +// Creates a vertical gradient shader. The caller owns the shader. +SkShader* CreateGradientShader(int start_point, + int end_point, + SkColor start_color, + SkColor end_color); + +// Converts COLORREFs (0BGR) to the ARGB layout Skia expects. +SkColor COLORREFToSkColor(COLORREF color); + +// Converts ARGB to COLORREFs (0BGR). +COLORREF SkColorToCOLORREF(SkColor color); + +} // namespace gfx + +#endif diff --git a/base/gfx/uniscribe.cc b/base/gfx/uniscribe.cc new file mode 100644 index 0000000..fbfb751 --- /dev/null +++ b/base/gfx/uniscribe.cc @@ -0,0 +1,872 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/gfx/uniscribe.h" + +#include "base/gfx/font_utils.h" +#include "base/logging.h" + +namespace gfx { + +// This function is used to see where word spacing should be applied inside +// runs. Note that this must match Font::treatAsSpace so we all agree where +// and how much space this is, so we don't want to do more general Unicode +// "is this a word break" thing. +static bool TreatAsSpace(wchar_t c) { + return c == ' ' || c == '\t' || c == '\n' || c == 0x00A0; +} + +// SCRIPT_FONTPROPERTIES contains glyph indices for default, invalid +// and blank glyphs. Just because ScriptShape succeeds does not mean +// that a text run is rendered correctly. Some characters may be rendered +// with default/invalid/blank glyphs. Therefore, we need to check if the glyph +// array returned by ScriptShape contains any of those glyphs to make +// sure that the text run is rendered successfully. +static bool ContainsMissingGlyphs(WORD *glyphs, + int length, + SCRIPT_FONTPROPERTIES* properties) { + for (int i = 0; i < length; ++i) { + if (glyphs[i] == properties->wgDefault || + (glyphs[i] == properties->wgInvalid && glyphs[i] != properties->wgBlank)) + return true; + } + + return false; +} + +// HFONT is the 'incarnation' of 'everything' about font, but it's an opaque +// handle and we can't directly query it to make a new HFONT sharing +// its characteristics (height, style, etc) except for family name. +// This function uses GetObject to convert HFONT back to LOGFONT, +// resets the fields of LOGFONT and calculates style to use later +// for the creation of a font identical to HFONT other than family name. +static void SetLogFontAndStyle(HFONT hfont, LOGFONT *logfont, int *style) { + DCHECK(hfont && logfont); + if (!hfont || !logfont) + return; + + GetObject(hfont, sizeof(LOGFONT), logfont); + // We reset these fields to values appropriate for CreateFontIndirect. + // while keeping lfHeight, which is the most important value in creating + // a new font similar to hfont. + logfont->lfWidth = 0; + logfont->lfEscapement = 0; + logfont->lfOrientation = 0; + logfont->lfCharSet = DEFAULT_CHARSET; + logfont->lfOutPrecision = OUT_TT_ONLY_PRECIS; + logfont->lfQuality = DEFAULT_QUALITY; // Honor user's desktop settings. + logfont->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + if (style) + *style = gfx::GetStyleFromLogfont(logfont); +} + +UniscribeState::UniscribeState(const wchar_t* input, + int input_length, + bool is_rtl, + HFONT hfont, + SCRIPT_CACHE* script_cache, + SCRIPT_FONTPROPERTIES* font_properties) + : input_(input), + input_length_(input_length), + is_rtl_(is_rtl), + hfont_(hfont), + script_cache_(script_cache), + font_properties_(font_properties), + directional_override_(false), + inhibit_ligate_(false), + letter_spacing_(0), + space_width_(0), + word_spacing_(0), + ascent_(0) { + logfont_.lfFaceName[0] = 0; +} + +UniscribeState::~UniscribeState() { +} + +void UniscribeState::InitWithOptionalLengthProtection(bool length_protection) { + // We cap the input length and just don't do anything. We'll allocate a lot + // of things of the size of the number of characters, so the allocated memory + // will be several times the input length. Plus shaping such a large buffer + // may be a form of denial of service. No legitimate text should be this long. + // It also appears that Uniscribe flatly rejects very long strings, so we + // don't lose anything by doing this. + // + // The input length protection may be disabled by the unit tests to cause + // an error condition. + static const int kMaxInputLength = 65535; + if (input_length_ == 0 || + (length_protection && input_length_ > kMaxInputLength)) + return; + + FillRuns(); + FillShapes(); + FillScreenOrder(); +} + +int UniscribeState::Width() const { + int width = 0; + for (int item_index = 0; item_index < static_cast<int>(runs_->size()); + item_index++) { + width += AdvanceForItem(item_index); + } + return width; +} + +void UniscribeState::Justify(int additional_space) { + // Count the total number of glyphs we have so we know how big to make the + // buffers below. + int total_glyphs = 0; + for (size_t run = 0; run < runs_->size(); run++) { + int run_idx = screen_order_[run]; + total_glyphs += static_cast<int>(shapes_[run_idx].glyph_length()); + } + if (total_glyphs == 0) + return; // Nothing to do. + + // We make one big buffer in screen order of all the glyphs we are drawing + // across runs so that the justification function will adjust evenly across + // all glyphs. + StackVector<SCRIPT_VISATTR, 64> visattr; + visattr->resize(total_glyphs); + StackVector<int, 64> advances; + advances->resize(total_glyphs); + StackVector<int, 64> justify; + justify->resize(total_glyphs); + + // Build the packed input. + int dest_index = 0; + for (size_t run = 0; run < runs_->size(); run++) { + int run_idx = screen_order_[run]; + const Shaping& shaping = shapes_[run_idx]; + + for (int i = 0; i < shaping.glyph_length(); i++, dest_index++) { + memcpy(&visattr[dest_index], &shaping.visattr[i], sizeof(SCRIPT_VISATTR)); + advances[dest_index] = shaping.advance[i]; + } + } + + // The documentation for ScriptJustify is wrong, the parameter is the space + // to add and not the width of the column you want. + const int min_kashida = 1; // How do we decide what this should be? + ScriptJustify(&visattr[0], &advances[0], total_glyphs, additional_space, + min_kashida, &justify[0]); + + // Now we have to unpack the justification amounts back into the runs so + // the glyph indices match. + int global_glyph_index = 0; + for (size_t run = 0; run < runs_->size(); run++) { + int run_idx = screen_order_[run]; + Shaping& shaping = shapes_[run_idx]; + + shaping.justify->resize(shaping.glyph_length()); + for (int i = 0; i < shaping.glyph_length(); i++, global_glyph_index++) + shaping.justify[i] = justify[global_glyph_index]; + } +} + +int UniscribeState::CharacterToX(int offset) const { + HRESULT hr; + DCHECK(offset <= input_length_); + + // Our algorithm is to traverse the items in screen order from left to + // right, adding in each item's screen width until we find the item with + // the requested character in it. + int width = 0; + for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) { + // Compute the length of this run. + int item_idx = screen_order_[screen_idx]; + const SCRIPT_ITEM& item = runs_[item_idx]; + const Shaping& shaping = shapes_[item_idx]; + int item_length = shaping.char_length(); + + if (offset >= item.iCharPos && offset <= item.iCharPos + item_length) { + // Character offset is in this run. + int char_len = offset - item.iCharPos; + + int cur_x = 0; + hr = ScriptCPtoX(char_len, FALSE, item_length, shaping.glyph_length(), + &shaping.logs[0], &shaping.visattr[0], + shaping.effective_advances(), &item.a, &cur_x); + if (FAILED(hr)) + return 0; + + width += cur_x + shaping.pre_padding; + DCHECK(width >= 0); + return width; + } + + // Move to the next item. + width += AdvanceForItem(item_idx); + } + DCHECK(width >= 0); + return width; +} + +int UniscribeState::XToCharacter(int x) const { + // We iterate in screen order until we find the item with the given pixel + // position in it. When we find that guy, we ask Uniscribe for the + // character index. + HRESULT hr; + for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) { + int item_idx = screen_order_[screen_idx]; + int advance_for_item = AdvanceForItem(item_idx); + + // Note that the run may be empty if shaping failed, so we want to skip + // over it. + const Shaping& shaping = shapes_[item_idx]; + int item_length = shaping.char_length(); + if (x <= advance_for_item && item_length > 0) { + // The requested offset is within this item. + const SCRIPT_ITEM& item = runs_[item_idx]; + + // Account for the leading space we've added to this run that Uniscribe + // doesn't know about. + x -= shaping.pre_padding; + + int char_x = 0; + int trailing; + hr = ScriptXtoCP(x, item_length, shaping.glyph_length(), + &shaping.logs[0], &shaping.visattr[0], + shaping.effective_advances(), &item.a, &char_x, + &trailing); + + // The character offset is within the item. We need to add the item's + // offset to transform it into the space of the TextRun + return char_x + item.iCharPos; + } + + // The offset is beyond this item, account for its length and move on. + x -= advance_for_item; + } + + // Error condition, we don't know what to do if we don't have that X + // position in any of our items. + return 0; +} + +void UniscribeState::Draw(HDC dc, int x, int y, int from, int to) { + HGDIOBJ old_font = 0; + int cur_x = x; + bool first_run = true; + + for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) { + int item_idx = screen_order_[screen_idx]; + const SCRIPT_ITEM& item = runs_[item_idx]; + const Shaping& shaping = shapes_[item_idx]; + + // Character offsets within this run. THESE MAY NOT BE IN RANGE and may + // be negative, etc. The code below handles this. + int from_char = from - item.iCharPos; + int to_char = to - item.iCharPos; + + // See if we need to draw any characters in this item. + if (shaping.char_length() == 0 || + from_char >= shaping.char_length() || to_char <= 0) { + // No chars in this item to display. + cur_x += AdvanceForItem(item_idx); + continue; + } + + // Compute the starting glyph within this span. |from| and |to| are + // global offsets that may intersect arbitrarily with our local run. + int from_glyph, after_glyph; + if (item.a.fRTL) { + // To compute the first glyph when going RTL, we use |to|. + if (to_char >= shaping.char_length()) { + // The end of the text is after (to the left) of us. + from_glyph = 0; + } else { + // Since |to| is exclusive, the first character we draw on the left + // is actually the one right before (to the right) of |to|. + from_glyph = shaping.logs[to_char - 1]; + } + + // The last glyph is actually the first character in the range. + if (from_char <= 0) { + // The first character to draw is before (to the right) of this span, + // so draw all the way to the end. + after_glyph = shaping.glyph_length(); + } else { + // We want to draw everything up until the character to the right of + // |from|. To the right is - 1, so we look that up (remember our + // character could be more than one glyph, so we can't look up our + // glyph and add one). + after_glyph = shaping.logs[from_char - 1]; + } + } else { + // Easy case, everybody agrees about directions. We only need to handle + // boundary conditions to get a range inclusive at the beginning, and + // exclusive at the ending. We have to do some computation to see the + // glyph one past the end. + from_glyph = shaping.logs[from_char < 0 ? 0 : from_char]; + if (to_char >= shaping.char_length()) + after_glyph = shaping.glyph_length(); + else + after_glyph = shaping.logs[to_char]; + } + + // Account for the characters that were skipped in this run. When + // WebKit asks us to draw a subset of the run, it actually tells us + // to draw at the X offset of the beginning of the run, since it + // doesn't know the internal position of any of our characters. + const int* effective_advances = shaping.effective_advances(); + int inner_offset = 0; + for (int i = 0; i < from_glyph; i++) + inner_offset += effective_advances[i]; + + // Actually draw the glyphs we found. + int glyph_count = after_glyph - from_glyph; + if (from_glyph >= 0 && glyph_count > 0) { + // Account for the preceeding space we need to add to this run. We don't + // need to count for the following space because that will be counted + // in AdvanceForItem below when we move to the next run. + inner_offset += shaping.pre_padding; + + // Pass NULL in when there is no justification. + const int* justify = shaping.justify->empty() ? + NULL : &shaping.justify[from_glyph]; + + if (first_run) { + old_font = SelectObject(dc, shaping.hfont_); + first_run = false; + } else { + SelectObject(dc, shaping.hfont_); + } + + // TODO(brettw) bug 698452: if a half a character is selected, + // we should set up a clip rect so we draw the half of the glyph + // correctly. + // Fonts with different ascents can be used to render different runs. + // 'Across-runs' y-coordinate correction needs to be adjusted + // for each font. + HRESULT hr = S_FALSE; + for (int executions = 0; executions < 2; ++executions) { + hr = ScriptTextOut(dc, shaping.script_cache_, cur_x + inner_offset, + y - shaping.ascent_offset_, 0, NULL, &item.a, NULL, + 0, &shaping.glyphs[from_glyph], + glyph_count, &shaping.advance[from_glyph], + justify, &shaping.offsets[from_glyph]); + if (S_OK != hr && 0 == executions) { + // If this ScriptTextOut is called from the renderer it might fail + // because the sandbox is preventing it from opening the font files. + // If we are running in the renderer, TryToPreloadFont is overridden + // to ask the browser to preload the font for us so we can access it. + TryToPreloadFont(shaping.hfont_); + continue; + } + break; + } + + DCHECK(S_OK == hr); + + + } + + cur_x += AdvanceForItem(item_idx); + } + + if (old_font) + SelectObject(dc, old_font); +} + +WORD UniscribeState::FirstGlyphForCharacter(int char_offset) const { + // Find the run for the given character. + for (int i = 0; i < static_cast<int>(runs_->size()); i++) { + int first_char = runs_[i].iCharPos; + const Shaping& shaping = shapes_[i]; + int local_offset = char_offset - first_char; + if (local_offset >= 0 && local_offset < shaping.char_length()) { + // The character is in this run, return the first glyph for it (should + // generally be the only glyph). It seems Uniscribe gives glyph 0 for + // empty, which is what we want to return in the "missing" case. + size_t glyph_index = shaping.logs[local_offset]; + if (glyph_index >= shaping.glyphs->size()) { + // The glyph should be in this run, but the run has too few actual + // characters. This can happen when shaping the run fails, in which + // case, we should have no data in the logs at all. + DCHECK(shaping.glyphs->empty()); + return 0; + } + return shaping.glyphs[glyph_index]; + } + } + return 0; +} + +void UniscribeState::FillRuns() { + HRESULT hr; + runs_->resize(UNISCRIBE_STATE_STACK_RUNS); + + SCRIPT_STATE input_state; + input_state.uBidiLevel = is_rtl_; + input_state.fOverrideDirection = directional_override_; + input_state.fInhibitSymSwap = false; + input_state.fCharShape = false; // Not implemented in Uniscribe + input_state.fDigitSubstitute = false; // Do we want this for Arabic? + input_state.fInhibitLigate = inhibit_ligate_; + input_state.fDisplayZWG = false; // Don't draw control characters. + input_state.fArabicNumContext = is_rtl_; // Do we want this for Arabic? + input_state.fGcpClusters = false; + input_state.fReserved = 0; + input_state.fEngineReserved = 0; + // The psControl argument to ScriptItemize should be non-NULL for RTL text, + // per http://msdn.microsoft.com/en-us/library/ms776532.aspx . So use a + // SCRIPT_CONTROL that is set to all zeros. Zero as a locale ID means the + // neutral locale per http://msdn.microsoft.com/en-us/library/ms776294.aspx . + static SCRIPT_CONTROL input_control = {0, // uDefaultLanguage :16; + 0, // fContextDigits :1; + 0, // fInvertPreBoundDir :1; + 0, // fInvertPostBoundDir :1; + 0, // fLinkStringBefore :1; + 0, // fLinkStringAfter :1; + 0, // fNeutralOverride :1; + 0, // fNumericOverride :1; + 0, // fLegacyBidiClass :1; + 0, // fMergeNeutralItems :1; + 0};// fReserved :7; + // Calling ScriptApplyDigitSubstitution( NULL, &input_control, &input_state) + // here would be appropriate if we wanted to set the language ID, and get + // local digit substitution behavior. For now, don't do it. + + while (true) { + int num_items = 0; + + // Ideally, we would have a way to know the runs before and after this + // one, and put them into the control parameter of ScriptItemize. This + // would allow us to shape characters properly that cross style + // boundaries (WebKit bug 6148). + // + // We tell ScriptItemize that the output list of items is one smaller + // than it actually is. According to Mozilla bug 366643, if there is + // not enough room in the array on pre-SP2 systems, ScriptItemize will + // write one past the end of the buffer. + // + // ScriptItemize is very strange. It will often require a much larger + // ITEM buffer internally than it will give us as output. For example, + // it will say a 16-item buffer is not big enough, and will write + // interesting numbers into all those items. But when we give it a 32 + // item buffer and it succeeds, it only has one item output. + // + // It seems to be doing at least two passes, the first where it puts a + // lot of intermediate data into our items, and the second where it + // collates them. + hr = ScriptItemize(input_, input_length_, + static_cast<int>(runs_->size()) - 1, &input_control, &input_state, + &runs_[0], &num_items); + if (SUCCEEDED(hr)) { + runs_->resize(num_items); + break; + } + if (hr != E_OUTOFMEMORY) { + // Some kind of unexpected error. + runs_->resize(0); + break; + } + // There was not enough items for it to write into, expand. + runs_->resize(runs_->size() * 2); + } + + // Fix up the directions of the items so they're what WebKit thinks + // they are. WebKit (and we assume any other caller) always knows what + // direction it wants things to be in, and will only give us runs that are in + // the same direction. Sometimes, Uniscibe disagrees, for example, if you + // have embedded ASCII punctuation in an Arabic string, WebKit will + // (correctly) know that is should still be rendered RTL, but Uniscibe might + // think LTR is better. + // + // TODO(brettw) bug 747235: + // This workaround fixes the bug but causes spacing problems in other cases. + // WebKit sometimes gives us a big run that includes ASCII and Arabic, and + // this forcing direction makes those cases incorrect. This seems to happen + // during layout only, so it ends up that spacing is incorrect (because being + // the wrong direction changes ligatures and stuff). + // + //for (size_t i = 0; i < runs_->size(); i++) + // runs_[i].a.fRTL = is_rtl_; +} + + +bool UniscribeState::Shape(const wchar_t* input, + int item_length, + int num_glyphs, + SCRIPT_ITEM& run, + Shaping& shaping) { + HFONT hfont = hfont_; + SCRIPT_CACHE* script_cache = script_cache_; + SCRIPT_FONTPROPERTIES* font_properties = font_properties_; + int ascent = ascent_; + HDC temp_dc = NULL; + HGDIOBJ old_font = 0; + HRESULT hr; + bool lastFallbackTried = false; + bool result; + + int generated_glyphs = 0; + + // In case HFONT passed in ctor cannot render this run, we have to scan + // other fonts from the beginning of the font list. + ResetFontIndex(); + + // Compute shapes. + while (true) { + shaping.logs->resize(item_length); + shaping.glyphs->resize(num_glyphs); + shaping.visattr->resize(num_glyphs); + + // Firefox sets SCRIPT_ANALYSIS.SCRIPT_STATE.fDisplayZWG to true + // here. Is that what we want? It will display control characters. + hr = ScriptShape(temp_dc, script_cache, input, item_length, + num_glyphs, &run.a, + &shaping.glyphs[0], &shaping.logs[0], + &shaping.visattr[0], &generated_glyphs); + if (hr == E_PENDING) { + // Allocate the DC. + temp_dc = GetDC(NULL); + old_font = SelectObject(temp_dc, hfont); + continue; + } else if (hr == E_OUTOFMEMORY) { + num_glyphs *= 2; + continue; + } else if (SUCCEEDED(hr) && + (lastFallbackTried || !ContainsMissingGlyphs(&shaping.glyphs[0], + generated_glyphs, font_properties))) { + break; + } + + // The current font can't render this run. clear DC and try + // next font. + if (temp_dc) { + SelectObject(temp_dc, old_font); + ReleaseDC(NULL, temp_dc); + temp_dc = NULL; + } + + if (NextWinFontData(&hfont, &script_cache, &font_properties, &ascent)) { + // The primary font does not support this run. Try next font. + // In case of web page rendering, they come from fonts specified in + // CSS stylesheets. + continue; + } else if (!lastFallbackTried) { + lastFallbackTried = true; + + // Generate a last fallback font based on the script of + // a character to draw while inheriting size and styles + // from the primary font + if (!logfont_.lfFaceName[0]) + SetLogFontAndStyle(hfont_, &logfont_, &style_); + + // TODO(jungshik): generic type should come from webkit for + // UniscribeStateTextRun (a derived class used in webkit). + const wchar_t *family = GetFallbackFamily(input, item_length, + GENERIC_FAMILY_STANDARD); + bool font_ok = GetDerivedFontData(family, style_, &logfont_, &ascent, &hfont, &script_cache); + + if (!font_ok) { + // If this GetDerivedFontData is called from the renderer it might fail + // because the sandbox is preventing it from opening the font files. + // If we are running in the renderer, TryToPreloadFont is overridden to + // ask the browser to preload the font for us so we can access it. + TryToPreloadFont(hfont); + + // Try again. + font_ok = GetDerivedFontData(family, style_, &logfont_, &ascent, &hfont, &script_cache); + DCHECK(font_ok); + } + + // TODO(jungshik) : Currently GetDerivedHFont always returns a + // a valid HFONT, but in the future, I may change it to return 0. + DCHECK(hfont); + + // We don't need a font_properties for the last resort fallback font + // because we don't have anything more to try and are forced to + // accept empty glyph boxes. If we tried a series of fonts as + // 'last-resort fallback', we'd need it, but currently, we don't. + continue; + } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { + run.a.eScript = SCRIPT_UNDEFINED; + continue; + } else if (FAILED(hr)) { + // Error shaping. + generated_glyphs = 0; + result = false; + goto cleanup; + } + } + + // Sets Windows font data for this run to those corresponding to + // a font supporting this run. we don't need to store font_properties + // because it's not used elsewhere. + shaping.hfont_ = hfont; + shaping.script_cache_ = script_cache; + + // The ascent of a font for this run can be different from + // that of the primary font so that we need to keep track of + // the difference per run and take that into account when calling + // ScriptTextOut in |Draw|. Otherwise, different runs rendered by + // different fonts would not be aligned vertically. + shaping.ascent_offset_ = ascent_ ? ascent - ascent_ : 0; + result = true; + +cleanup: + shaping.glyphs->resize(generated_glyphs); + shaping.visattr->resize(generated_glyphs); + shaping.advance->resize(generated_glyphs); + shaping.offsets->resize(generated_glyphs); + if (temp_dc) { + SelectObject(temp_dc, old_font); + ReleaseDC(NULL, temp_dc); + } + // On failure, our logs don't mean anything, so zero those out. + if (!result) + shaping.logs->clear(); + + return result; +} + +void UniscribeState::FillShapes() { + shapes_->resize(runs_->size()); + for (size_t i = 0; i < runs_->size(); i++) { + int start_item = runs_[i].iCharPos; + int item_length = input_length_ - start_item; + if (i < runs_->size() - 1) + item_length = runs_[i + 1].iCharPos - start_item; + + int num_glyphs; + if (item_length < UNISCRIBE_STATE_STACK_CHARS) { + // We'll start our buffer sizes with the current stack space available + // in our buffers if the current input fits. As long as it + // doesn't expand past that we'll save a lot of time mallocing. + num_glyphs = UNISCRIBE_STATE_STACK_CHARS; + } else { + // When the input doesn't fit, give up with the stack since it will + // almost surely not be enough room (unless the input actually shrinks, + // which is unlikely) and just start with the length recommended by + // the Uniscribe documentation as a "usually fits" size. + num_glyphs = item_length * 3 / 2 + 16; + } + + // Convert a string to a glyph string trying the primary font, + // fonts in the fallback list and then script-specific last resort font. + Shaping& shaping = shapes_[i]; + if (!Shape(&input_[start_item], item_length, num_glyphs, runs_[i], shaping)) + continue; + + // Compute placements. Note that offsets is documented incorrectly + // and is actually an array. + + // DC that we lazily create if Uniscribe commands us to. + // (this does not happen often because script_cache is already + // updated when calling ScriptShape). + HDC temp_dc = NULL; + HGDIOBJ old_font = NULL; + HRESULT hr; + while (true) { + shaping.pre_padding = 0; + hr = ScriptPlace(temp_dc, shaping.script_cache_, &shaping.glyphs[0], + static_cast<int>(shaping.glyphs->size()), + &shaping.visattr[0], &runs_[i].a, + &shaping.advance[0], &shaping.offsets[0], + &shaping.abc); + if (hr != E_PENDING) + break; + + // Allocate the DC and run the loop again. + temp_dc = GetDC(NULL); + old_font = SelectObject(temp_dc, shaping.hfont_); + } + + if (FAILED(hr)) { + // Some error we don't know how to handle. Nuke all of our data + // since we can't deal with partially valid data later. + runs_->clear(); + shapes_->clear(); + screen_order_->clear(); + } + + if (temp_dc) { + SelectObject(temp_dc, old_font); + ReleaseDC(NULL, temp_dc); + } + } + + AdjustSpaceAdvances(); + + if (letter_spacing_ != 0 || word_spacing_ != 0) + ApplySpacing(); +} + +void UniscribeState::FillScreenOrder() { + screen_order_->resize(runs_->size()); + + // We assume that the input has only one text direction in it. + // TODO(brettw) are we sure we want to keep this restriction? + if (is_rtl_) { + for (int i = 0; i < static_cast<int>(screen_order_->size()); i++) + screen_order_[static_cast<int>(screen_order_->size()) - i - 1] = i; + } else { + for (int i = 0; i < static_cast<int>(screen_order_->size()); i++) + screen_order_[i] = i; + } +} + +void UniscribeState::AdjustSpaceAdvances() { + if (space_width_ == 0) + return; + + int space_width_without_letter_spacing = space_width_ - letter_spacing_; + + // This mostly matches what WebKit's UniscribeController::shapeAndPlaceItem. + for (size_t run = 0; run < runs_->size(); run++) { + Shaping& shaping = shapes_[run]; + + for (int i = 0; i < shaping.char_length(); i++) { + if (!TreatAsSpace(input_[runs_[run].iCharPos + i])) + continue; + + int glyph_index = shaping.logs[i]; + int current_advance = shaping.advance[glyph_index]; + // Don't give zero-width spaces a width. + if (!current_advance) + continue; + + // current_advance does not include additional letter-spacing, but + // space_width does. Here we find out how off we are from the correct + // width for the space not including letter-spacing, then just subtract + // that diff. + int diff = current_advance - space_width_without_letter_spacing; + // The shaping can consist of a run of text, so only subtract the + // difference in the width of the glyph. + shaping.advance[glyph_index] -= diff; + shaping.abc.abcB -= diff; + } + } +} + +void UniscribeState::ApplySpacing() { + for (size_t run = 0; run < runs_->size(); run++) { + Shaping& shaping = shapes_[run]; + bool is_rtl = runs_[run].a.fRTL; + + if (letter_spacing_ != 0) { + // RTL text gets padded to the left of each character. We increment the + // run's advance to make this happen. This will be balanced out by NOT + // adding additional advance to the last glyph in the run. + if (is_rtl) + shaping.pre_padding += letter_spacing_; + + // Go through all the glyphs in this run and increase the "advance" to + // account for letter spacing. We adjust letter spacing only on cluster + // boundaries. + // + // This works for most scripts, but may have problems with some indic + // scripts. This behavior is better than Firefox or IE for Hebrew. + for (int i = 0; i < shaping.glyph_length(); i++) { + if (shaping.visattr[i].fClusterStart) { + // Ick, we need to assign the extra space so that the glyph comes + // first, then is followed by the space. This is opposite for RTL. + if (is_rtl) { + if (i != shaping.glyph_length() - 1) { + // All but the last character just get the spacing applied to + // their advance. The last character doesn't get anything, + shaping.advance[i] += letter_spacing_; + shaping.abc.abcB += letter_spacing_; + } + } else { + // LTR case is easier, we just add to the advance. + shaping.advance[i] += letter_spacing_; + shaping.abc.abcB += letter_spacing_; + } + } + } + } + + // Go through all the characters to find whitespace and insert the extra + // wordspacing amount for the glyphs they correspond to. + if (word_spacing_ != 0) { + for (int i = 0; i < shaping.char_length(); i++) { + if (!TreatAsSpace(input_[runs_[run].iCharPos + i])) + continue; + + // The char in question is a word separator... + int glyph_index = shaping.logs[i]; + + // Spaces will not have a glyph in Uniscribe, it will just add + // additional advance to the character to the left of the space. The + // space's corresponding glyph will be the character following it in + // reading order. + if (is_rtl) { + // In RTL, the glyph to the left of the space is the same as the + // first glyph of the following character, so we can just increment + // it. + shaping.advance[glyph_index] += word_spacing_; + shaping.abc.abcB += word_spacing_; + } else { + // LTR is actually more complex here, we apply it to the previous + // character if there is one, otherwise we have to apply it to the + // leading space of the run. + if (glyph_index == 0) { + shaping.pre_padding += word_spacing_; + } else { + shaping.advance[glyph_index - 1] += word_spacing_; + shaping.abc.abcB += word_spacing_; + } + } + } + } // word_spacing_ != 0 + + // Loop for next run... + } +} + +// The advance is the ABC width of the run +int UniscribeState::AdvanceForItem(int item_index) const { + int accum = 0; + const Shaping& shaping = shapes_[item_index]; + + if (shaping.justify->empty()) { + // Easy case with no justification, the width is just the ABC width of t + // the run. (The ABC width is the sum of the advances). + return shaping.abc.abcA + shaping.abc.abcB + shaping.abc.abcC + + shaping.pre_padding; + } + + // With justification, we use the justified amounts instead. The + // justification array contains both the advance and the extra space + // added for justification, so is the width we want. + int justification = 0; + for (size_t i = 0; i < shaping.justify->size(); i++) + justification += shaping.justify[i]; + + return shaping.pre_padding + justification; +} + +} // namespace gfx diff --git a/base/gfx/uniscribe.h b/base/gfx/uniscribe.h new file mode 100644 index 0000000..29f0d811 --- /dev/null +++ b/base/gfx/uniscribe.h @@ -0,0 +1,390 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// A wrapper around Uniscribe that provides a reasonable API. + +#ifndef BASE_GFX_UNISCRIBE_H__ +#define BASE_GFX_UNISCRIBE_H__ + +#include <windows.h> +#include <usp10.h> +#include <wchar.h> +#include <map> +#include <vector> + +#include "base/stack_container.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +namespace gfx { + +#define UNISCRIBE_STATE_STACK_RUNS 8 +#define UNISCRIBE_STATE_STACK_CHARS 32 + +// This object should be safe to create & destroy frequently, as long as the +// caller preserves the script_cache when possible (this data may be slow to +// compute). +// +// This object is "kind of large" (~1K) because it reserves a lot of space for +// working with to avoid expensive heap operations. Therefore, not only should +// you not worry about creating and destroying it, you should try to not keep +// them around. +class UniscribeState { + public: + // Initializes this Uniscribe run with the text pointed to by |run| with + // |length|. The input is NOT null terminated. + // + // The is_rtl flag should be set if the input script is RTL. It is assumed + // that the caller has already divided up the input text (using ICU, for + // example) into runs of the same direction of script. This avoids + // disagreements between the caller and Uniscribe later (see FillItems). + // + // A script cache should be provided by the caller that is initialized to + // NULL. When the caller is done with the cache (it may be stored between + // runs as long as it is used consistently with the same HFONT), it should + // call ScriptFreeCache(). + UniscribeState(const wchar_t* input, + int input_length, + bool is_rtl, + HFONT hfont, + SCRIPT_CACHE* script_cache, + SCRIPT_FONTPROPERTIES* font_properties); + + virtual ~UniscribeState(); + + // Sets Uniscribe's directional override flag. False by default. + bool directional_override() const { + return directional_override_; + } + void set_directional_override(bool override) { + directional_override_ = override; + } + + // Set's Uniscribe's no-ligate override flag. False by default. + bool inhibit_ligate() const { + return inhibit_ligate_; + } + void set_inhibit_ligate(bool inhibit) { + inhibit_ligate_ = inhibit; + } + + // Set letter spacing. We will try to insert this much space between + // graphemes (one or more glyphs perceived as a single unit by ordinary users + // of a script). Positive values increase letter spacing, negative values + // decrease it. 0 by default. + int letter_spacing() const { + return letter_spacing_; + } + void set_letter_spacing(int letter_spacing) { + letter_spacing_ = letter_spacing; + } + + // Set the width of a standard space character. We use this to normalize + // space widths. Windows will make spaces after Hindi characters larger than + // other spaces. A space_width of 0 means to use the default space width. + // + // Must be set before Init() is called. + int space_width() const { + return space_width_; + } + void set_space_width(int space_width) { + space_width_ = space_width; + } + + // Set word spacing. We will try to insert this much extra space between + // each word in the input (beyond whatever whitespace character separates + // words). Positive values lead to increased letter spacing, negative values + // decrease it. 0 by default. + // + // Must be set before Init() is called. + int word_spacing() const { + return word_spacing_; + } + void set_word_spacing(int word_spacing) { + word_spacing_ = word_spacing; + } + void set_ascent(int ascent) { + ascent_ = ascent; + } + + // You must call this after setting any options but before doing any + // other calls like asking for widths or drawing. + void Init() { InitWithOptionalLengthProtection(true); } + + // Returns the total width in pixels of the text run. + int Width() const; + + // Call to justify the text, with the amount of space that should be ADDED to + // get the desired width that the column should be justified to. Normally, + // spaces are inserted, but for Arabic there will be kashidas (extra strokes) + // inserted instead. + // + // This function MUST be called AFTER Init(). + void Justify(int additional_space); + + // Computes the given character offset into a pixel offset of the beginning + // of that character. + int CharacterToX(int offset) const; + + // Converts the given pixel X position into a logical character offset into + // the run. For positions appearing before the first character, this will + // return -1. + int XToCharacter(int x) const; + + // Draws the given characters to (x, y) in the given DC. The font will be + // handled by this function, but the font color and other attributes should + // be pre-set. + // + // The y position is the upper left corner, NOT the baseline. + void Draw(HDC dc, int x, int y, int from, int to); + + // Returns the first glyph assigned to the character at the given offset. + // This function is used to retrieve glyph information when Uniscribe is + // being used to generate glyphs for non-complex, non-BMP (above U+FFFF) + // characters. These characters are not otherwise special and have no + // complex shaping rules, so we don't otherwise need Uniscribe, except + // Uniscribe is the only way to get glyphs for non-BMP characters. + // + // Returns 0 if there is no glyph for the given character. + WORD FirstGlyphForCharacter(int char_offset) const; + + protected: + // Backend for init. The flag allows the unit test to specify whether we + // should fail early for very long strings like normal, or try to pass the + // long string to Uniscribe. The latter provides a way to force failure of + // shaping. + void InitWithOptionalLengthProtection(bool length_protection); + + // Tries to preload the font when the it is not accessible. + // This is the default implementation and it does not do anything. + virtual void TryToPreloadFont(HFONT font) {} + + private: + FRIEND_TEST(UniscribeTest, TooBig); + + // An array corresponding to each item in runs_ containing information + // on each of the glyphs that were generated. Like runs_, this is in + // reading order. However, for rtl text, the characters within each + // item will be reversed. + struct Shaping { + Shaping() + : pre_padding(0), + hfont_(NULL), + script_cache_(NULL), + ascent_offset_(0) { + abc.abcA = 0; + abc.abcB = 0; + abc.abcC = 0; + } + + // Returns the number of glyphs (which will be drawn to the screen) + // in this run. + int glyph_length() const { + return static_cast<int>(glyphs->size()); + } + + // Returns the number of characters (that we started with) in this run. + int char_length() const { + return static_cast<int>(logs->size()); + } + + // Returns the advance array that should be used when measuring glyphs. + // The returned pointer will indicate an array with glyph_length() elements + // and the advance that should be used for each one. This is either the + // real advance, or the justified advances if there is one, and is the + // array we want to use for measurement. + const int* effective_advances() const { + if (advance->empty()) + return 0; + if (justify->empty()) + return &advance[0]; + return &justify[0]; + } + + // This is the advance amount of space that we have added to the beginning + // of the run. It is like the ABC's |A| advance but one that we create and + // must handle internally whenever computing with pixel offsets. + int pre_padding; + + // Glyph indices in the font used to display this item. These indices + // are in screen order. + StackVector<WORD, UNISCRIBE_STATE_STACK_CHARS> glyphs; + + // For each input character, this tells us the first glyph index it + // generated. This is the only array with size of the input chars. + // + // All offsets are from the beginning of this run. Multiple characters can + // generate one glyph, in which case there will be adjacent duplicates in + // this list. One character can also generate multiple glyphs, in which + // case there will be skipped indices in this list. + StackVector<WORD, UNISCRIBE_STATE_STACK_CHARS> logs; + + // Flags and such for each glyph. + StackVector<SCRIPT_VISATTR, UNISCRIBE_STATE_STACK_CHARS> visattr; + + // Horizontal advances for each glyph listed above, this is basically + // how wide each glyph is. + StackVector<int, UNISCRIBE_STATE_STACK_CHARS> advance; + + // This contains glyph offsets, from the nominal position of a glyph. It + // is used to adjust the positions of multiple combining characters + // around/above/below base characters in a context-sensitive manner so + // that they don't bump against each other and the base character. + StackVector<GOFFSET, UNISCRIBE_STATE_STACK_CHARS> offsets; + + // Filled by a call to Justify, this is empty for nonjustified text. + // If nonempty, this contains the array of justify characters for each + // character as returned by ScriptJustify. + // + // This is the same as the advance array, but with extra space added for + // some characters. The difference between a glyph's |justify| width and + // it's |advance| width is the extra space added. + StackVector<int, UNISCRIBE_STATE_STACK_CHARS> justify; + + // Sizing information for this run. This treats the entire run as a + // character with a preceeding advance, width, and ending advance. + // The B width is the sum of the |advance| array, and the A and C widths + // are any extra spacing applied to each end. + // + // It is unclear from the documentation what this actually means. From + // experimentation, it seems that the sum of the character advances is + // always the sum of the ABC values, and I'm not sure what you're supposed + // to do with the ABC values. + ABC abc; + + // Pointers to windows font data used to render this run. + HFONT hfont_; + SCRIPT_CACHE* script_cache_; + + // Ascent offset between the ascent of the primary font + // and that of the fallback font. The offset needs to be applied, + // when drawing a string, to align multiple runs rendered with + // different fonts. + int ascent_offset_; + }; + + // Computes the runs_ array from the text run. + void FillRuns(); + + // Computes the shapes_ array given an runs_ array already filled in. + void FillShapes(); + + // Fills in the screen_order_ array (see below). + void FillScreenOrder(); + + // Called to update the glyph positions based on the current spacing options + // that are set. + void ApplySpacing(); + + // Normalizes all advances for spaces to the same width. This keeps windows + // from making spaces after Hindi characters larger, which is then + // inconsistent with our meaure of the width since WebKit doesn't include + // spaces in text-runs sent to uniscribe unless white-space:pre. + void AdjustSpaceAdvances(); + + // Returns the total width of a single item. + int AdvanceForItem(int item_index) const; + + // Shapes a run (pointed to by |input|) using |hfont| first. + // Tries a series of fonts specified retrieved with NextWinFontData + // and finally a font covering characters in |*input|. A string pointed + // by |input| comes from ScriptItemize and is supposed to contain + // characters belonging to a single script aside from characters + // common to all scripts (e.g. space). + bool Shape(const wchar_t* input, + int item_length, + int num_glyphs, + SCRIPT_ITEM& run, + Shaping& shaping); + + // Gets Windows font data for the next best font to try in the list + // of fonts. When there's no more font available, returns false + // without touching any of out params. Need to call ResetFontIndex + // to start scanning of the font list from the beginning. + virtual bool NextWinFontData(HFONT* hfont, + SCRIPT_CACHE** script_cache, + SCRIPT_FONTPROPERTIES** font_properties, + int* ascent) { + return false; + } + + // Resets the font index to the first in the list of fonts + // to try after the primaryFont turns out not to work. With font_index + // reset, NextWinFontData scans fallback fonts from the beginning. + virtual void ResetFontIndex() {} + + // The input data for this run of Uniscribe. See the constructor. + const wchar_t* input_; + const int input_length_; + const bool is_rtl_; + + // Windows font data for the primary font : + // In a sense, logfont_ and style_ are redundant because + // hfont_ contains all the information. However, invoking GetObject, + // everytime we need the height and the style, is rather expensive so + // that we cache them. Would it be better to add getter and (virtual) + // setter for the height and the style of the primary font, instead of + // logfont_? Then, a derived class ctor can set ascent_, height_ and style_ + // if they're known. Getters for them would have to 'infer' their values from + // hfont_ ONLY when they're not set. + HFONT hfont_; + SCRIPT_CACHE* script_cache_; + SCRIPT_FONTPROPERTIES* font_properties_; + int ascent_; + LOGFONT logfont_; + int style_; + + // Options, see the getters/setters above. + bool directional_override_; + bool inhibit_ligate_; + int letter_spacing_; + int space_width_; + int word_spacing_; + int justification_width_; + + // Uniscribe breaks the text into Runs. These are one length of text that is + // in one script and one direction. This array is in reading order. + StackVector<SCRIPT_ITEM, UNISCRIBE_STATE_STACK_RUNS> runs_; + + StackVector<Shaping, UNISCRIBE_STATE_STACK_RUNS> shapes_; + + // This is a mapping between reading order and screen order for the items. + // Uniscribe's items array are in reading order. For right-to-left text, + // or mixed (although WebKit's |TextRun| should really be only one + // direction), this makes it very difficult to compute character offsets + // and positions. This list is in screen order from left to right, and + // gives the index into the |runs_| and |shapes_| arrays of each + // subsequent item. + StackVector<int, UNISCRIBE_STATE_STACK_RUNS> screen_order_; + + DISALLOW_EVIL_CONSTRUCTORS(UniscribeState); +}; + +} // namespace gfx + +#endif // BASE_GFX_UNISCRIBE_H__ diff --git a/base/gfx/uniscribe_unittest.cc b/base/gfx/uniscribe_unittest.cc new file mode 100644 index 0000000..8b2419c --- /dev/null +++ b/base/gfx/uniscribe_unittest.cc @@ -0,0 +1,164 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/uniscribe.h" +#include "base/win_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This must be in the gfx namespace for the friend statements in uniscribe.h +// to work. +namespace gfx { + +namespace { + +class UniscribeTest : public testing::Test { + public: + UniscribeTest() { + } + + // Returns an HFONT with the given name. The caller does not have to free + // this, it will be automatically freed at the end of the test. Returns NULL + // on failure. On success, the + HFONT MakeFont(const wchar_t* font_name, SCRIPT_CACHE** cache) { + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + lf.lfHeight = 20; + wcscpy_s(lf.lfFaceName, font_name); + + HFONT hfont = CreateFontIndirect(&lf); + if (!hfont) + return NULL; + + *cache = new SCRIPT_CACHE; + **cache = NULL; + created_fonts_.push_back(std::make_pair(hfont, *cache)); + return hfont; + } + + protected: + // Default font properties structure for tests to use. + SCRIPT_FONTPROPERTIES properties_; + + private: + virtual void SetUp() { + memset(&properties_, 0, sizeof(SCRIPT_FONTPROPERTIES)); + properties_.cBytes = sizeof(SCRIPT_FONTPROPERTIES); + properties_.wgBlank = ' '; + properties_.wgDefault = '?'; // Used when the character is not in the font. + properties_.wgInvalid = '#'; // Used for invalid characters. + } + + virtual void TearDown() { + // Free any allocated fonts. + for (size_t i = 0; i < created_fonts_.size(); i++) { + DeleteObject(created_fonts_[i].first); + ScriptFreeCache(created_fonts_[i].second); + delete created_fonts_[i].second; + } + created_fonts_.clear(); + } + + // Tracks allocated fonts so we can delete them at the end of the test. + // The script cache pointer is heap allocated and must be freed. + std::vector< std::pair<HFONT, SCRIPT_CACHE*> > created_fonts_; + + DISALLOW_EVIL_CONSTRUCTORS(UniscribeTest); +}; + +} // namespace + +// This test tests giving Uniscribe a very large buffer, which will cause a +// failure. +TEST_F(UniscribeTest, TooBig) { + // This test will only run on Windows XP. It seems Uniscribe does not have the + // internal limit on Windows 2000 that we rely on to cause this failure. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + + // Make a large string with an e with a zillion combining accents. + std::wstring input(L"e"); + for (int i = 0; i < 100000; i++) + input.push_back(0x301); // Combining acute accent. + + SCRIPT_CACHE* script_cache; + HFONT hfont = MakeFont(L"Times New Roman", &script_cache); + ASSERT_TRUE(hfont); + + // Test a long string without the normal length protection we have. This will + // cause shaping to fail. + { + gfx::UniscribeState uniscribe(input.data(), static_cast<int>(input.size()), + false, hfont, script_cache, &properties_); + uniscribe.InitWithOptionalLengthProtection(false); + + // There should be one shaping entry, with nothing in it. + ASSERT_EQ(1, uniscribe.shapes_->size()); + EXPECT_EQ(0, uniscribe.shapes_[0].glyphs->size()); + EXPECT_EQ(0, uniscribe.shapes_[0].logs->size()); + EXPECT_EQ(0, uniscribe.shapes_[0].visattr->size()); + EXPECT_EQ(0, uniscribe.shapes_[0].advance->size()); + EXPECT_EQ(0, uniscribe.shapes_[0].offsets->size()); + EXPECT_EQ(0, uniscribe.shapes_[0].justify->size()); + EXPECT_EQ(0, uniscribe.shapes_[0].abc.abcA); + EXPECT_EQ(0, uniscribe.shapes_[0].abc.abcB); + EXPECT_EQ(0, uniscribe.shapes_[0].abc.abcC); + + // The sizes of the other stuff should match the shaping entry. + EXPECT_EQ(1, uniscribe.runs_->size()); + EXPECT_EQ(1, uniscribe.screen_order_->size()); + + // Check that the various querying functions handle the empty case properly. + EXPECT_EQ(0, uniscribe.Width()); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(0)); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(1000)); + EXPECT_EQ(0, uniscribe.XToCharacter(0)); + EXPECT_EQ(0, uniscribe.XToCharacter(1000)); + } + + // Now test the very large string and make sure it is handled properly by the + // length protection. + { + gfx::UniscribeState uniscribe(input.data(), static_cast<int>(input.size()), + false, hfont, script_cache, &properties_); + uniscribe.InitWithOptionalLengthProtection(true); + + // There should be 0 runs and shapes. + EXPECT_EQ(0, uniscribe.runs_->size()); + EXPECT_EQ(0, uniscribe.shapes_->size()); + EXPECT_EQ(0, uniscribe.screen_order_->size()); + + EXPECT_EQ(0, uniscribe.Width()); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(0)); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(1000)); + EXPECT_EQ(0, uniscribe.XToCharacter(0)); + EXPECT_EQ(0, uniscribe.XToCharacter(1000)); + } +} + +} // namespace gfx
\ No newline at end of file diff --git a/base/gfx/vector_canvas.cc b/base/gfx/vector_canvas.cc new file mode 100644 index 0000000..6f02f11 --- /dev/null +++ b/base/gfx/vector_canvas.cc @@ -0,0 +1,109 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/vector_canvas.h" + +#include "base/gfx/vector_device.h" +#include "base/logging.h" + +namespace gfx { + +VectorCanvas::VectorCanvas() { +} + +VectorCanvas::VectorCanvas(HDC dc, int width, int height) { + initialize(dc, width, height); +} + +VectorCanvas::~VectorCanvas() { +} + +void VectorCanvas::initialize(HDC context, int width, int height) { + SkDevice* device = createPlatformDevice(width, height, true, context); + setDevice(device); + device->unref(); // was created with refcount 1, and setDevice also refs +} + +SkBounder* VectorCanvas::setBounder(SkBounder* bounder) { + if (!IsTopDeviceVectorial()) + return PlatformCanvas::setBounder(bounder); + + // This function isn't used in the code. Verify this assumption. + NOTREACHED(); + return NULL; +} + +SkDevice* VectorCanvas::createDevice(SkBitmap::Config config, + int width, int height, + bool is_opaque, bool isForLayer) { + DCHECK(config == SkBitmap::kARGB_8888_Config); + return createPlatformDevice(width, height, is_opaque, NULL); +} + +SkDrawFilter* VectorCanvas::setDrawFilter(SkDrawFilter* filter) { + // This function isn't used in the code. Verify this assumption. + NOTREACHED(); + return NULL; +} + +SkDevice* VectorCanvas::createPlatformDevice(int width, + int height, bool is_opaque, + HANDLE shared_section) { + if (!is_opaque) { + // TODO(maruel): http://b/1184002 1184002 When restoring a semi-transparent + // layer, i.e. merging it, we need to rasterize it because GDI doesn't + // support transparency except for AlphaBlend(). Right now, a + // BitmapPlatformDevice is created when VectorCanvas think a saveLayers() + // call is being done. The way to save a layer would be to create an + // EMF-based VectorDevice and have this device registers the drawing. When + // playing back the device into a bitmap, do it at the printer's dpi instead + // of the layout's dpi (which is much lower). + return PlatformCanvas::createPlatformDevice(width, height, is_opaque, + shared_section); + } + + // TODO(maruel): http://b/1183870 Look if it would be worth to increase the + // resolution by ~10x (any worthy factor) to increase the rendering precision + // (think about printing) while using a relatively low dpi. This happens + // because we receive float as input but the GDI functions works with + // integers. The idea is to premultiply the matrix with this factor and + // multiply each SkScalar that are passed to SkScalarRound(value) as + // SkScalarRound(value * 10). Safari is already doing the same for text + // rendering. + DCHECK(shared_section); + PlatformDevice* device = VectorDevice::create( + reinterpret_cast<HDC>(shared_section), width, height); + return device; +} + +bool VectorCanvas::IsTopDeviceVectorial() const { + return getTopPlatformDevice().IsVectorial(); +} + +} // namespace gfx diff --git a/base/gfx/vector_canvas.h b/base/gfx/vector_canvas.h new file mode 100644 index 0000000..d6a5703 --- /dev/null +++ b/base/gfx/vector_canvas.h @@ -0,0 +1,70 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_VECTOR_CANVAS_H__ +#define BASE_GFX_VECTOR_CANVAS_H__ + +#include "base/gfx/platform_canvas.h" +#include "base/gfx/vector_device.h" + +namespace gfx { + +// This class is a specialization of the regular PlatformCanvas. It is designed +// to work with a VectorDevice to manage platform-specific drawing. It allows +// using both Skia operations and platform-specific operations. It *doesn't* +// support reading back from the bitmap backstore since it is not used. +class VectorCanvas : public PlatformCanvas { + public: + VectorCanvas(); + VectorCanvas(HDC dc, int width, int height); + virtual ~VectorCanvas(); + + // For two-part init, call if you use the no-argument constructor above + void initialize(HDC context, int width, int height); + + virtual SkBounder* setBounder(SkBounder*); + virtual SkDevice* createDevice(SkBitmap::Config config, + int width, int height, + bool is_opaque, bool isForLayer); + virtual SkDrawFilter* setDrawFilter(SkDrawFilter* filter); + + private: + // |is_opaque| is unused. |shared_section| is in fact the HDC used for output. + virtual SkDevice* createPlatformDevice(int width, int height, bool is_opaque, + HANDLE shared_section); + + // Returns true if the top device is vector based and not bitmap based. + bool IsTopDeviceVectorial() const; + + DISALLOW_EVIL_CONSTRUCTORS(VectorCanvas); +}; + +} // namespace gfx + +#endif // BASE_GFX_VECTOR_CANVAS_H__ diff --git a/base/gfx/vector_canvas_unittest.cc b/base/gfx/vector_canvas_unittest.cc new file mode 100644 index 0000000..57ff34d --- /dev/null +++ b/base/gfx/vector_canvas_unittest.cc @@ -0,0 +1,1032 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/vector_canvas.h" + +#include <vector> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/gfx/bitmap_header.h" +#include "base/gfx/png_decoder.h" +#include "base/gfx/png_encoder.h" +#include "base/gfx/size.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "SkDashPathEffect.h" + +namespace { + +const wchar_t* const kGenerateSwitch = L"vector-canvas-generate"; + +// Base class for unit test that uses data. It initializes a directory path +// based on the test's name. +class DataUnitTest : public testing::Test { + public: + DataUnitTest(const std::wstring& base_path) : base_path_(base_path) { } + + protected: + // Load the test's data path. + virtual void SetUp() { + const testing::TestInfo& test_info = + *testing::UnitTest::GetInstance()->current_test_info(); + PathService::Get(base::DIR_SOURCE_ROOT, &test_dir_); + file_util::AppendToPath(&test_dir_, base_path_); + file_util::AppendToPath(&test_dir_, L"data"); + file_util::AppendToPath(&test_dir_, + ASCIIToWide(test_info.test_case_name())); + file_util::AppendToPath(&test_dir_, ASCIIToWide(test_info.name())); + + // Hack for a quick lowercase. We assume all the tests names are ASCII. + std::string tmp(WideToASCII(test_dir_)); + for (size_t i = 0; i < tmp.size(); ++i) + tmp[i] = ToLowerASCII(tmp[i]); + test_dir_ = ASCIIToWide(tmp); + } + + // Returns the fully qualified path of directory containing test data files. + const std::wstring& test_dir() const { + return test_dir_; + } + + // Returns the fully qualified path of a data file. + std::wstring test_file(const std::wstring& filename) const { + // Hack for a quick lowercase. We assume all the test data file names are + // ASCII. + std::string tmp(WideToASCII(filename)); + for (size_t i = 0; i < tmp.size(); ++i) + tmp[i] = ToLowerASCII(tmp[i]); + + std::wstring path(test_dir()); + file_util::AppendToPath(&path, ASCIIToWide(tmp)); + return path; + } + + private: + // Path where the unit test is coming from: base, net, chrome, etc. + std::wstring base_path_; + + // Path to directory used to contain the test data. + std::wstring test_dir_; + + DISALLOW_EVIL_CONSTRUCTORS(DataUnitTest); +}; + +// Lightweight HDC management. +class Context { + public: + Context() : context_(CreateCompatibleDC(NULL)) { + EXPECT_TRUE(context_); + } + ~Context() { + DeleteDC(context_); + } + + HDC context() const { return context_; } + + private: + HDC context_; + + DISALLOW_EVIL_CONSTRUCTORS(Context); +}; + +// Lightweight HBITMAP management. +class Bitmap { + public: + Bitmap(const Context& context, int x, int y) { + BITMAPINFOHEADER hdr; + gfx::CreateBitmapHeader(x, y, &hdr); + bitmap_ = CreateDIBSection(context.context(), + reinterpret_cast<BITMAPINFO*>(&hdr), 0, + &data_, NULL, 0); + EXPECT_TRUE(bitmap_); + EXPECT_TRUE(SelectObject(context.context(), bitmap_)); + } + ~Bitmap() { + EXPECT_TRUE(DeleteObject(bitmap_)); + } + + private: + HBITMAP bitmap_; + + void* data_; + + DISALLOW_EVIL_CONSTRUCTORS(Bitmap); +}; + +// Lightweight raw-bitmap management. The image, once initialized, is immuable. +// It is mainly used for comparison. +class Image { + public: + // Creates the image from the given filename on disk. + Image(const std::wstring& filename) : ignore_alpha_(true) { + std::string compressed; + file_util::ReadFileToString(filename, &compressed); + EXPECT_TRUE(compressed.size()); + + int w; + int h; + EXPECT_TRUE(PNGDecoder::Decode( + reinterpret_cast<const unsigned char*>(compressed.c_str()), + compressed.size(), PNGDecoder::FORMAT_BGRA, &data_, &w, &h)); + size_.SetSize(w, h); + row_length_ = w * sizeof(uint32); + } + + // Loads the image from a canvas. + Image(const gfx::PlatformCanvas& canvas) : ignore_alpha_(true) { + // Use a different way to access the bitmap. The normal way would be to + // query the SkBitmap. + HDC context = canvas.getTopPlatformDevice().getBitmapDC(); + HGDIOBJ bitmap = GetCurrentObject(context, OBJ_BITMAP); + EXPECT_TRUE(bitmap != NULL); + // Initialize the clip region to the entire bitmap. + BITMAP bitmap_data; + EXPECT_EQ(GetObject(bitmap, sizeof(BITMAP), &bitmap_data), + sizeof(BITMAP)); + size_.SetSize(bitmap_data.bmWidth, bitmap_data.bmHeight); + row_length_ = bitmap_data.bmWidthBytes; + size_t size = row_length_ * size_.height(); + data_.resize(size); + memcpy(&*data_.begin(), bitmap_data.bmBits, size); + } + + // Loads the image from a canvas. + Image(const SkBitmap& bitmap) : ignore_alpha_(true) { + SkAutoLockPixels lock(bitmap); + size_.SetSize(bitmap.width(), bitmap.height()); + row_length_ = static_cast<int>(bitmap.rowBytes()); + size_t size = row_length_ * size_.height(); + data_.resize(size); + memcpy(&*data_.begin(), bitmap.getAddr(0, 0), size); + } + + const gfx::Size& size() const { + return size_; + } + + int row_length() const { + return row_length_; + } + + // Save the image to a png file. Used to create the initial test files. + void SaveToFile(const std::wstring& filename) { + std::vector<unsigned char> compressed; + ASSERT_TRUE(PNGEncoder::Encode(&*data_.begin(), + PNGEncoder::FORMAT_BGRA, + size_.width(), + size_.height(), + row_length_, + true, + &compressed)); + ASSERT_TRUE(compressed.size()); + FILE* f; + ASSERT_EQ(_wfopen_s(&f, filename.c_str(), L"wbS"), 0); + ASSERT_EQ(fwrite(&*compressed.begin(), 1, compressed.size(), f), + compressed.size()); + fclose(f); + } + + // Returns the percentage of the image that is different from the other, + // between 0 and 100. + double PercentageDifferent(const Image& rhs) const { + if (size_ != rhs.size_ || row_length_ != rhs.row_length_ || + size_.width() == 0 || size_.height() == 0) + return 100.; // When of different size or empty, they are 100% different. + + // Compute pixels different in the overlap + int pixels_different = 0; + for (int y = 0; y < size_.height(); ++y) { + for (int x = 0; x < size_.width(); ++x) { + uint32_t lhs_pixel = pixel_at(x, y); + uint32_t rhs_pixel = rhs.pixel_at(x, y); + if (lhs_pixel != rhs_pixel) + ++pixels_different; + } + } + + // Like the WebKit ImageDiff tool, we define percentage different in terms + // of the size of the 'actual' bitmap. + double total_pixels = static_cast<double>(size_.width()) * + static_cast<double>(size_.height()); + return static_cast<double>(pixels_different) / total_pixels * 100.; + } + + // Returns the 0x0RGB or 0xARGB value of the pixel at the given location, + // depending on ignore_alpha_. + uint32 pixel_at(int x, int y) const { + EXPECT_TRUE(x >= 0 && x < size_.width()); + EXPECT_TRUE(y >= 0 && y < size_.height()); + const uint32* data = reinterpret_cast<const uint32*>(&*data_.begin()); + const uint32* data_row = data + y * row_length_ / sizeof(uint32); + if (ignore_alpha_) + return data_row[x] & 0xFFFFFF; // Strip out A. + else + return data_row[x]; + } + + private: + // Pixel dimensions of the image. + gfx::Size size_; + + // Length of a line in bytes. + int row_length_; + + // Actual bitmap data in arrays of RGBAs (so when loaded as uint32, it's + // 0xABGR). + std::vector<unsigned char> data_; + + // Flag to signal if the comparison functions should ignore the alpha channel. + const bool ignore_alpha_; + + DISALLOW_EVIL_CONSTRUCTORS(Image); +}; + +// Base for tests. Capability to process an image. +class ImageTest : public DataUnitTest { + public: + typedef DataUnitTest parent; + + // In what state is the test running. + enum ProcessAction { + GENERATE, + COMPARE, + NOOP, + }; + + ImageTest(const std::wstring& base_path, ProcessAction default_action) + : parent(base_path), + action_(default_action) { + } + + protected: + virtual void SetUp() { + parent::SetUp(); + + if (action_ == GENERATE) { + // Make sure the directory exist. + file_util::CreateDirectory(test_dir()); + } + } + + // Compares or saves the bitmap currently loaded in the context, depending on + // kGenerating value. Returns 0 on success or any positive value between ]0, + // 100] on failure. The return value is the percentage of difference between + // the image in the file and the image in the canvas. + double ProcessCanvas(const gfx::PlatformCanvas& canvas, + std::wstring filename) const { + filename += L".png"; + switch (action_) { + case GENERATE: + SaveImage(canvas, filename); + return 0.; + case COMPARE: + return CompareImage(canvas, filename); + case NOOP: + return 0; + default: + // Invalid state, returns that the image is 100 different. + return 100.; + } + } + + // Compares the bitmap currently loaded in the context with the file. Returns + // the percentage of pixel difference between both images, between 0 and 100. + double CompareImage(const gfx::PlatformCanvas& canvas, + const std::wstring& filename) const { + Image image1(canvas); + Image image2(test_file(filename)); + double diff = image1.PercentageDifferent(image2); + return diff; + } + + // Saves the bitmap currently loaded in the context into the file. + void SaveImage(const gfx::PlatformCanvas& canvas, + const std::wstring& filename) const { + Image(canvas).SaveToFile(test_file(filename)); + } + + ProcessAction action_; + + DISALLOW_EVIL_CONSTRUCTORS(ImageTest); +}; + +// Premultiply the Alpha channel on the R, B and G channels. +void Premultiply(SkBitmap bitmap) { + SkAutoLockPixels lock(bitmap); + for (int x = 0; x < bitmap.width(); ++x) { + for (int y = 0; y < bitmap.height(); ++y) { + uint32_t* pixel_addr = bitmap.getAddr32(x, y); + uint32_t color = *pixel_addr; + BYTE alpha = SkColorGetA(color); + if (!alpha) { + *pixel_addr = 0; + } else { + BYTE alpha_offset = alpha / 2; + *pixel_addr = SkColorSetARGB( + SkColorGetA(color), + (SkColorGetR(color) * 255 + alpha_offset) / alpha, + (SkColorGetG(color) * 255 + alpha_offset) / alpha, + (SkColorGetB(color) * 255 + alpha_offset) / alpha); + } + } + } +} + +void LoadPngFileToSkBitmap(const std::wstring& file, SkBitmap* bitmap) { + std::string compressed; + file_util::ReadFileToString(file, &compressed); + EXPECT_TRUE(compressed.size()); + // Extra-lame. If you care, fix it. + std::vector<unsigned char> data; + data.assign(reinterpret_cast<const unsigned char*>(compressed.c_str()), + reinterpret_cast<const unsigned char*>(compressed.c_str() + + compressed.size())); + EXPECT_TRUE(PNGDecoder::Decode(&data, bitmap)); + EXPECT_FALSE(bitmap->isOpaque()); + Premultiply(*bitmap); +} + +} // namespace + +// Streams an image. +inline std::ostream& operator<<(std::ostream& out, const Image& image) { + return out << "Image(" << image.size() << ", " << image.row_length() << ")"; +} + +// Runs simultaneously the same drawing commands on VectorCanvas and +// PlatformCanvas and compare the results. +class VectorCanvasTest : public ImageTest { + public: + typedef ImageTest parent; + + VectorCanvasTest() : parent(L"base", CurrentMode()), compare_canvas_(true) { + } + + protected: + virtual void SetUp() { + parent::SetUp(); + Init(100); + number_ = 0; + } + + virtual void TearDown() { + delete pcanvas_; + pcanvas_ = NULL; + + delete vcanvas_; + vcanvas_ = NULL; + + delete bitmap_; + bitmap_ = NULL; + + delete context_; + context_ = NULL; + + parent::TearDown(); + } + + void Init(int size) { + size_ = size; + context_ = new Context(); + bitmap_ = new Bitmap(*context_, size_, size_); + vcanvas_ = new gfx::VectorCanvas(context_->context(), size_, size_); + pcanvas_ = new gfx::PlatformCanvas(size_, size_, false); + + // Clear white. + vcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode); + pcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode); + } + + // Compares both canvas and returns the pixel difference in percentage between + // both images. 0 on success and ]0, 100] on failure. + double ProcessImage(const std::wstring& filename) { + std::wstring number(StringPrintf(L"%02d_", number_++)); + double diff1 = parent::ProcessCanvas(*vcanvas_, number + L"vc_" + filename); + double diff2 = parent::ProcessCanvas(*pcanvas_, number + L"pc_" + filename); + if (!compare_canvas_) + return std::max(diff1, diff2); + + Image image1(*vcanvas_); + Image image2(*pcanvas_); + double diff = image1.PercentageDifferent(image2); + return std::max(std::max(diff1, diff2), diff); + } + + // Returns COMPARE, which is the default. If kGenerateSwitch command + // line argument is used to start this process, GENERATE is returned instead. + static ProcessAction CurrentMode() { + return CommandLine().HasSwitch(kGenerateSwitch) ? GENERATE : COMPARE; + } + + // Length in x and y of the square canvas. + int size_; + + // Current image number in the current test. Used to number of test files. + int number_; + + // A temporary HDC to draw into. + Context* context_; + + // Bitmap created inside context_. + Bitmap* bitmap_; + + // Vector based canvas. + gfx::VectorCanvas* vcanvas_; + + // Pixel based canvas. + gfx::PlatformCanvas* pcanvas_; + + // When true (default), vcanvas_ and pcanvas_ contents are compared and + // verified to be identical. + bool compare_canvas_; +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Actual tests + +TEST_F(VectorCanvasTest, Uninitialized) { + // Do a little mubadumba do get uninitialized stuff. + VectorCanvasTest::TearDown(); + + // The goal is not to verify that have the same uninitialized data. + compare_canvas_ = false; + + context_ = new Context(); + bitmap_ = new Bitmap(*context_, size_, size_); + vcanvas_ = new gfx::VectorCanvas(context_->context(), size_, size_); + pcanvas_ = new gfx::PlatformCanvas(size_, size_, false); + + // VectorCanvas default initialization is black. + // PlatformCanvas default initialization is almost white 0x01FFFEFD (invalid + // Skia color) in both Debug and Release. See magicTransparencyColor in + // platform_device.cc + EXPECT_EQ(0., ProcessImage(L"empty")); +} + +TEST_F(VectorCanvasTest, BasicDrawing) { + EXPECT_EQ(Image(*vcanvas_).PercentageDifferent(Image(*pcanvas_)), 0.) + << L"clean"; + EXPECT_EQ(0., ProcessImage(L"clean")); + + // Clear white. + { + vcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode); + pcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode); + } + EXPECT_EQ(0., ProcessImage(L"drawARGB")); + + // Diagonal line top-left to bottom-right. + { + SkPaint paint; + // Default color is black. + vcanvas_->drawLine(10, 10, 90, 90, paint); + pcanvas_->drawLine(10, 10, 90, 90, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawLine_black")); + + // Rect. + { + SkPaint paint; + paint.setColor(SK_ColorGREEN); + vcanvas_->drawRectCoords(25, 25, 75, 75, paint); + pcanvas_->drawRectCoords(25, 25, 75, 75, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawRect_green")); + + // A single-point rect doesn't leave any mark. + { + SkPaint paint; + paint.setColor(SK_ColorBLUE); + vcanvas_->drawRectCoords(5, 5, 5, 5, paint); + pcanvas_->drawRectCoords(5, 5, 5, 5, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawRect_noop")); + + // Rect. + { + SkPaint paint; + paint.setColor(SK_ColorBLUE); + vcanvas_->drawRectCoords(75, 50, 80, 55, paint); + pcanvas_->drawRectCoords(75, 50, 80, 55, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawRect_noop")); + + // Empty again + { + vcanvas_->drawPaint(SkPaint()); + pcanvas_->drawPaint(SkPaint()); + } + EXPECT_EQ(0., ProcessImage(L"drawPaint_black")); + + // Horizontal line left to right. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(10, 20, 90, 20, paint); + pcanvas_->drawLine(10, 20, 90, 20, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawLine_left_to_right")); + + // Vertical line downward. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(30, 10, 30, 90, paint); + pcanvas_->drawLine(30, 10, 30, 90, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawLine_red")); +} + +TEST_F(VectorCanvasTest, Circles) { + // There is NO WAY to make them agree. At least verify that the output doesn't + // change across versions. This test is disabled. See bug 1060231. + compare_canvas_ = false; + + // Stroked Circle. + { + SkPaint paint; + SkPath path; + path.addCircle(50, 75, 10); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorMAGENTA); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"circle_stroke")); + + // Filled Circle. + { + SkPaint paint; + SkPath path; + path.addCircle(50, 25, 10); + paint.setStyle(SkPaint::kFill_Style); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"circle_fill")); + + // Stroked Circle over. + { + SkPaint paint; + SkPath path; + path.addCircle(50, 25, 10); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLUE); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"circle_over_strike")); + + // Stroke and Fill Circle. + { + SkPaint paint; + SkPath path; + path.addCircle(12, 50, 10); + paint.setStyle(SkPaint::kStrokeAndFill_Style); + paint.setColor(SK_ColorRED); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"circle_stroke_and_fill")); + + // Line + Quad + Cubic. + { + SkPaint paint; + SkPath path; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorGREEN); + path.moveTo(1, 1); + path.lineTo(60, 40); + path.lineTo(80, 80); + path.quadTo(20, 50, 10, 90); + path.quadTo(50, 20, 90, 10); + path.cubicTo(20, 40, 50, 50, 10, 10); + path.cubicTo(30, 20, 50, 50, 90, 10); + path.addRect(90, 90, 95, 96); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"mixed_stroke")); +} + +TEST_F(VectorCanvasTest, LineOrientation) { + // There is NO WAY to make them agree. At least verify that the output doesn't + // change across versions. This test is disabled. See bug 1060231. + compare_canvas_ = false; + + // Horizontal lines. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + // Left to right. + vcanvas_->drawLine(10, 20, 90, 20, paint); + pcanvas_->drawLine(10, 20, 90, 20, paint); + // Right to left. + vcanvas_->drawLine(90, 30, 10, 30, paint); + pcanvas_->drawLine(90, 30, 10, 30, paint); + } + EXPECT_EQ(0., ProcessImage(L"horizontal")); + + // Vertical lines. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + // Top down. + vcanvas_->drawLine(20, 10, 20, 90, paint); + pcanvas_->drawLine(20, 10, 20, 90, paint); + // Bottom up. + vcanvas_->drawLine(30, 90, 30, 10, paint); + pcanvas_->drawLine(30, 90, 30, 10, paint); + } + EXPECT_EQ(0., ProcessImage(L"vertical")); + + // Try again with a 180 degres rotation. + vcanvas_->rotate(180); + pcanvas_->rotate(180); + + // Horizontal lines (rotated). + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(-10, -25, -90, -25, paint); + pcanvas_->drawLine(-10, -25, -90, -25, paint); + vcanvas_->drawLine(-90, -35, -10, -35, paint); + pcanvas_->drawLine(-90, -35, -10, -35, paint); + } + EXPECT_EQ(0., ProcessImage(L"horizontal_180")); + + // Vertical lines (rotated). + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(-25, -10, -25, -90, paint); + pcanvas_->drawLine(-25, -10, -25, -90, paint); + vcanvas_->drawLine(-35, -90, -35, -10, paint); + pcanvas_->drawLine(-35, -90, -35, -10, paint); + } + EXPECT_EQ(0., ProcessImage(L"vertical_180")); +} + +TEST_F(VectorCanvasTest, PathOrientation) { + // There is NO WAY to make them agree. At least verify that the output doesn't + // change across versions. This test is disabled. See bug 1060231. + compare_canvas_ = false; + + // Horizontal lines. + { + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorRED); + SkPath path; + SkPoint start; + start.set(10, 20); + SkPoint end; + end.set(90, 20); + path.moveTo(start); + path.lineTo(end); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawPath_ltr")); + + // Horizontal lines. + { + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorRED); + SkPath path; + SkPoint start; + start.set(90, 30); + SkPoint end; + end.set(10, 30); + path.moveTo(start); + path.lineTo(end); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"drawPath_rtl")); +} + +TEST_F(VectorCanvasTest, DiagonalLines) { + SkPaint paint; + paint.setColor(SK_ColorRED); + + vcanvas_->drawLine(10, 10, 90, 90, paint); + pcanvas_->drawLine(10, 10, 90, 90, paint); + EXPECT_EQ(0., ProcessImage(L"nw-se")); + + // Starting here, there is NO WAY to make them agree. At least verify that the + // output doesn't change across versions. This test is disabled. See bug + // 1060231. + compare_canvas_ = false; + + vcanvas_->drawLine(10, 95, 90, 15, paint); + pcanvas_->drawLine(10, 95, 90, 15, paint); + EXPECT_EQ(0., ProcessImage(L"sw-ne")); + + vcanvas_->drawLine(90, 10, 10, 90, paint); + pcanvas_->drawLine(90, 10, 10, 90, paint); + EXPECT_EQ(0., ProcessImage(L"ne-sw")); + + vcanvas_->drawLine(95, 90, 15, 10, paint); + pcanvas_->drawLine(95, 90, 15, 10, paint); + EXPECT_EQ(0., ProcessImage(L"se-nw")); +} + +TEST_F(VectorCanvasTest, PathEffects) { + { + SkPaint paint; + SkScalar intervals[] = { 1, 1 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + vcanvas_->drawLine(10, 10, 90, 10, paint); + pcanvas_->drawLine(10, 10, 90, 10, paint); + } + EXPECT_EQ(0., ProcessImage(L"dash_line")); + + + // Starting here, there is NO WAY to make them agree. At least verify that the + // output doesn't change across versions. This test is disabled. See bug + // 1060231. + compare_canvas_ = false; + + { + SkPaint paint; + SkScalar intervals[] = { 3, 5 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + path.moveTo(10, 15); + path.lineTo(90, 15); + path.lineTo(90, 90); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(L"dash_path")); + + { + SkPaint paint; + SkScalar intervals[] = { 2, 1 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + vcanvas_->drawRectCoords(20, 20, 30, 30, paint); + pcanvas_->drawRectCoords(20, 20, 30, 30, paint); + } + EXPECT_EQ(0., ProcessImage(L"dash_rect")); + + // This thing looks like it has been drawn by a 3 years old kid. I haven't + // filed a bug on this since I guess nobody is expecting this to look nice. + { + SkPaint paint; + SkScalar intervals[] = { 1, 1 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + path.addCircle(50, 75, 10); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + EXPECT_EQ(0., ProcessImage(L"circle")); + } +} + +TEST_F(VectorCanvasTest, Bitmaps) { + // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests + // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't + // really care about Windows 2000 pixel colors. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"bitmap_opaque.png"), &bitmap); + vcanvas_->drawBitmap(bitmap, 13, 3, NULL); + pcanvas_->drawBitmap(bitmap, 13, 3, NULL); + EXPECT_EQ(0., ProcessImage(L"opaque")); + } + + { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"bitmap_alpha.png"), &bitmap); + vcanvas_->drawBitmap(bitmap, 5, 15, NULL); + pcanvas_->drawBitmap(bitmap, 5, 15, NULL); + EXPECT_EQ(0., ProcessImage(L"alpha")); + } +} + +TEST_F(VectorCanvasTest, ClippingRect) { + // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests + // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't + // really care about Windows 2000 pixel colors. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap); + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + + vcanvas_->drawBitmap(bitmap, 13, 3, NULL); + pcanvas_->drawBitmap(bitmap, 13, 3, NULL); + EXPECT_EQ(0., ProcessImage(L"rect")); +} + +TEST_F(VectorCanvasTest, ClippingPath) { + // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests + // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't + // really care about Windows 2000 pixel colors. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap); + SkPath path; + path.addCircle(20, 20, 10); + vcanvas_->clipPath(path); + pcanvas_->clipPath(path); + + vcanvas_->drawBitmap(bitmap, 14, 3, NULL); + pcanvas_->drawBitmap(bitmap, 14, 3, NULL); + EXPECT_EQ(0., ProcessImage(L"path")); +} + +TEST_F(VectorCanvasTest, ClippingCombined) { + // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests + // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't + // really care about Windows 2000 pixel colors. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap); + + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + SkPath path; + path.addCircle(20, 20, 10); + vcanvas_->clipPath(path, SkRegion::kUnion_Op); + pcanvas_->clipPath(path, SkRegion::kUnion_Op); + + vcanvas_->drawBitmap(bitmap, 15, 3, NULL); + pcanvas_->drawBitmap(bitmap, 15, 3, NULL); + EXPECT_EQ(0., ProcessImage(L"combined")); +} + +TEST_F(VectorCanvasTest, ClippingIntersect) { + // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests + // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't + // really care about Windows 2000 pixel colors. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap); + + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + SkPath path; + path.addCircle(23, 23, 15); + vcanvas_->clipPath(path); + pcanvas_->clipPath(path); + + vcanvas_->drawBitmap(bitmap, 15, 3, NULL); + pcanvas_->drawBitmap(bitmap, 15, 3, NULL); + EXPECT_EQ(0., ProcessImage(L"intersect")); +} + +TEST_F(VectorCanvasTest, ClippingClean) { + // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests + // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't + // really care about Windows 2000 pixel colors. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap); + { + SkRegion old_region(pcanvas_->getTotalClip()); + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + + vcanvas_->drawBitmap(bitmap, 15, 3, NULL); + pcanvas_->drawBitmap(bitmap, 15, 3, NULL); + EXPECT_EQ(0., ProcessImage(L"clipped")); + vcanvas_->clipRegion(old_region, SkRegion::kReplace_Op); + pcanvas_->clipRegion(old_region, SkRegion::kReplace_Op); + } + { + // Verify that the clipping region has been fixed back. + vcanvas_->drawBitmap(bitmap, 55, 3, NULL); + pcanvas_->drawBitmap(bitmap, 55, 3, NULL); + EXPECT_EQ(0., ProcessImage(L"unclipped")); + } +} + +TEST_F(VectorCanvasTest, Matrix) { + // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests + // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't + // really care about Windows 2000 pixel colors. + if (win_util::GetWinVersion() <= win_util::WINVERSION_2000) + return; + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap); + { + vcanvas_->translate(15, 3); + pcanvas_->translate(15, 3); + vcanvas_->drawBitmap(bitmap, 0, 0, NULL); + pcanvas_->drawBitmap(bitmap, 0, 0, NULL); + EXPECT_EQ(0., ProcessImage(L"translate1")); + } + { + vcanvas_->translate(-30, -23); + pcanvas_->translate(-30, -23); + vcanvas_->drawBitmap(bitmap, 0, 0, NULL); + pcanvas_->drawBitmap(bitmap, 0, 0, NULL); + EXPECT_EQ(0., ProcessImage(L"translate2")); + } + vcanvas_->resetMatrix(); + pcanvas_->resetMatrix(); + + // For scaling and rotation, they use a different algorithm (nearest + // neighborhood vs smoothing). At least verify that the output doesn't change + // across versions. + compare_canvas_ = false; + + { + vcanvas_->scale(SkDoubleToScalar(1.9), SkDoubleToScalar(1.5)); + pcanvas_->scale(SkDoubleToScalar(1.9), SkDoubleToScalar(1.5)); + vcanvas_->drawBitmap(bitmap, 1, 1, NULL); + pcanvas_->drawBitmap(bitmap, 1, 1, NULL); + EXPECT_EQ(0., ProcessImage(L"scale")); + } + vcanvas_->resetMatrix(); + pcanvas_->resetMatrix(); + + { + vcanvas_->rotate(67); + pcanvas_->rotate(67); + vcanvas_->drawBitmap(bitmap, 20, -50, NULL); + pcanvas_->drawBitmap(bitmap, 20, -50, NULL); + EXPECT_EQ(0., ProcessImage(L"rotate")); + } +} diff --git a/base/gfx/vector_device.cc b/base/gfx/vector_device.cc new file mode 100644 index 0000000..c7e20f6 --- /dev/null +++ b/base/gfx/vector_device.cc @@ -0,0 +1,646 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/gfx/vector_device.h" + +#include "base/gfx/bitmap_header.h" +#include "base/gfx/skia_utils.h" +#include "base/logging.h" +#include "base/scoped_handle.h" + +#include "SkUtils.h" + +namespace gfx { + +VectorDevice* VectorDevice::create(HDC dc, int width, int height) { + InitializeDC(dc); + + // Link the SkBitmap to the current selected bitmap in the device context. + SkBitmap bitmap; + HGDIOBJ selected_bitmap = GetCurrentObject(dc, OBJ_BITMAP); + bool succeeded = false; + if (selected_bitmap != NULL) { + BITMAP bitmap_data; + if (GetObject(selected_bitmap, sizeof(BITMAP), &bitmap_data) == + sizeof(BITMAP)) { + // The context has a bitmap attached. Attach our SkBitmap to it. + // Warning: If the bitmap gets unselected from the HDC, VectorDevice has + // no way to detect this, so the HBITMAP could be released while SkBitmap + // still has a reference to it. Be cautious. + if (width == bitmap_data.bmWidth && + height == bitmap_data.bmHeight) { + bitmap.setConfig(SkBitmap::kARGB_8888_Config, + bitmap_data.bmWidth, + bitmap_data.bmHeight, + bitmap_data.bmWidthBytes); + bitmap.setPixels(bitmap_data.bmBits); + succeeded = true; + } + } + } + + if (!succeeded) + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + + return new VectorDevice(dc, bitmap); +} + +VectorDevice::VectorDevice(HDC dc, const SkBitmap& bitmap) + : PlatformDevice(bitmap), + hdc_(dc), + previous_brush_(NULL), + previous_pen_(NULL), + offset_x_(0), + offset_y_(0) { + transform_.reset(); +} + +VectorDevice::~VectorDevice() { + DCHECK(previous_brush_ == NULL); + DCHECK(previous_pen_ == NULL); +} + + +void VectorDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { + // TODO(maruel): Bypass the current transformation matrix. + SkRect rect; + rect.fLeft = 0; + rect.fTop = 0; + rect.fRight = SkIntToScalar(width() + 1); + rect.fBottom = SkIntToScalar(height() + 1); + drawRect(draw, rect, paint); +} + +void VectorDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, + size_t count, const SkPoint pts[], + const SkPaint& paint) { + if (!count) + return; + + if (mode == SkCanvas::kPoints_PointMode) { + NOTREACHED(); + return; + } + + SkPaint tmp_paint(paint); + tmp_paint.setStyle(SkPaint::kStroke_Style); + + // Draw a path instead. + SkPath path; + switch (mode) { + case SkCanvas::kLines_PointMode: + if (count % 2) { + NOTREACHED(); + return; + } + for (size_t i = 0; i < count / 2; ++i) { + path.moveTo(pts[2 * i]); + path.lineTo(pts[2 * i + 1]); + } + break; + case SkCanvas::kPolygon_PointMode: + path.moveTo(pts[0]); + for (size_t i = 1; i < count; ++i) { + path.lineTo(pts[i]); + } + break; + default: + NOTREACHED(); + return; + } + // Draw the calculated path. + drawPath(draw, path, tmp_paint); +} + +void VectorDevice::drawRect(const SkDraw& draw, const SkRect& rect, + const SkPaint& paint) { + if (paint.getPathEffect()) { + // Draw a path instead. + SkPath path_orginal; + path_orginal.addRect(rect); + + // Apply the path effect to the rect. + SkPath path_modified; + paint.getFillPath(path_orginal, &path_modified); + + // Removes the path effect from the temporary SkPaint object. + SkPaint paint_no_effet(paint); + paint_no_effet.setPathEffect(NULL)->safeUnref(); + + // Draw the calculated path. + drawPath(draw, path_modified, paint_no_effet); + return; + } + + if (!ApplyPaint(paint)) { + return; + } + HDC dc = getBitmapDC(); + if (!Rectangle(dc, SkScalarRound(rect.fLeft), + SkScalarRound(rect.fTop), + SkScalarRound(rect.fRight), + SkScalarRound(rect.fBottom))) { + NOTREACHED(); + } + Cleanup(); +} + +void VectorDevice::drawPath(const SkDraw& draw, const SkPath& path, + const SkPaint& paint) { + if (paint.getPathEffect()) { + // Apply the path effect forehand. + SkPath path_modified; + paint.getFillPath(path, &path_modified); + + // Removes the path effect from the temporary SkPaint object. + SkPaint paint_no_effet(paint); + paint_no_effet.setPathEffect(NULL)->safeUnref(); + + // Draw the calculated path. + drawPath(draw, path_modified, paint_no_effet); + return; + } + + if (!ApplyPaint(paint)) { + return; + } + HDC dc = getBitmapDC(); + PlatformDevice::LoadPathToDC(dc, path); + switch (paint.getStyle()) { + case SkPaint::kFill_Style: { + BOOL res = StrokeAndFillPath(dc); + DCHECK(res != 0); + break; + } + case SkPaint::kStroke_Style: { + BOOL res = StrokePath(dc); + DCHECK(res != 0); + break; + } + case SkPaint::kStrokeAndFill_Style: { + BOOL res = StrokeAndFillPath(dc); + DCHECK(res != 0); + break; + } + default: + NOTREACHED(); + break; + } + Cleanup(); +} + +void VectorDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint) { + // Load the temporary matrix. This is what will translate, rotate and resize + // the bitmap. + SkMatrix actual_transform(transform_); + actual_transform.preConcat(matrix); + LoadTransformToDC(hdc_, actual_transform); + + InternalDrawBitmap(bitmap, 0, 0, paint); + + // Restore the original matrix. + LoadTransformToDC(hdc_, transform_); +} + +void VectorDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) { + SkMatrix identity; + identity.reset(); + LoadTransformToDC(hdc_, identity); + + InternalDrawBitmap(bitmap, x, y, paint); + + // Restore the original matrix. + LoadTransformToDC(hdc_, transform_); +} + +void VectorDevice::drawText(const SkDraw& draw, const void* text, size_t byteLength, + SkScalar x, SkScalar y, const SkPaint& paint) { + // This function isn't used in the code. Verify this assumption. + NOTREACHED(); +} + +void VectorDevice::drawPosText(const SkDraw& draw, const void* text, size_t len, + const SkScalar pos[], SkScalar constY, + int scalarsPerPos, const SkPaint& paint) { + // This function isn't used in the code. Verify this assumption. + NOTREACHED(); +} + +void VectorDevice::drawTextOnPath(const SkDraw& draw, const void* text, + size_t len, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + // This function isn't used in the code. Verify this assumption. + NOTREACHED(); +} + +void VectorDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode, + int vertexCount, + const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) { + // This function isn't used in the code. Verify this assumption. + NOTREACHED(); +} + +void VectorDevice::drawDevice(const SkDraw& draw, SkDevice* device, int x, + int y, const SkPaint& paint) { + // TODO(maruel): http://b/1183870 Playback the EMF buffer at printer's dpi if + // it is a vectorial device. + drawSprite(draw, device->accessBitmap(false), x, y, paint); +} + +bool VectorDevice::ApplyPaint(const SkPaint& paint) { + // Note: The goal here is to transfert the SkPaint's state to the HDC's state. + // This function does not execute the SkPaint drawing commands. These should + // be executed in drawPaint(). + + SkPaint::Style style = paint.getStyle(); + if (!paint.getAlpha()) + style = SkPaint::kStyleCount; + + switch (style) { + case SkPaint::kFill_Style: + if (!CreateBrush(true, paint) || + !CreatePen(false, paint)) + return false; + break; + case SkPaint::kStroke_Style: + if (!CreateBrush(false, paint) || + !CreatePen(true, paint)) + return false; + break; + case SkPaint::kStrokeAndFill_Style: + if (!CreateBrush(true, paint) || + !CreatePen(true, paint)) + return false; + break; + default: + if (!CreateBrush(false, paint) || + !CreatePen(false, paint)) + return false; + break; + } + + /* + getFlags(); + isAntiAlias(); + isDither() + isLinearText() + isSubpixelText() + isUnderlineText() + isStrikeThruText() + isFakeBoldText() + isDevKernText() + isFilterBitmap() + + // Skia's text is not used. This should be fixed. + getTextAlign() + getTextScaleX() + getTextSkewX() + getTextEncoding() + getFontMetrics() + getFontSpacing() + */ + + // BUG 1094907: Implement shaders. Shaders currently in use: + // SkShader::CreateBitmapShader + // SkGradientShader::CreateRadial + // SkGradientShader::CreateLinear + // DCHECK(!paint.getShader()); + + // http://b/1106647 Implement loopers and mask filter. Looper currently in + // use: + // SkBlurDrawLooper is used for shadows. + // DCHECK(!paint.getLooper()); + // DCHECK(!paint.getMaskFilter()); + + // http://b/1165900 Implement xfermode. + // DCHECK(!paint.getXfermode()); + + // The path effect should be processed before arriving here. + DCHECK(!paint.getPathEffect()); + + // These aren't used in the code. Verify this assumption. + DCHECK(!paint.getColorFilter()); + DCHECK(!paint.getRasterizer()); + // Reuse code to load Win32 Fonts. + DCHECK(!paint.getTypeface()); + return true; +} + +void VectorDevice::setMatrixClip(const SkMatrix& transform, + const SkRegion& region) { + transform_ = transform; + LoadTransformToDC(hdc_, transform_); + clip_region_ = region; + if (!clip_region_.isEmpty()) + LoadClipRegion(); +} + +void VectorDevice::setDeviceOffset(int x, int y) { + offset_x_ = x; + offset_y_ = y; +} + +void VectorDevice::drawToHDC(HDC dc, int x, int y, const RECT* src_rect) { + NOTREACHED(); +} + +void VectorDevice::LoadClipRegion() { + // We don't use transform_ for the clipping region since the translation is + // already applied to offset_x_ and offset_y_. + SkMatrix t; + t.reset(); + t.postTranslate(SkIntToScalar(-offset_x_), SkIntToScalar(-offset_y_)); + LoadClippingRegionToDC(hdc_, clip_region_, t); +} + +bool VectorDevice::CreateBrush(bool use_brush, COLORREF color) { + DCHECK(previous_brush_ == NULL); + // We can't use SetDCBrushColor() or DC_BRUSH when drawing to a EMF buffer. + // SetDCBrushColor() calls are not recorded at all and DC_BRUSH will use + // WHITE_BRUSH instead. + + if (!use_brush) { + // Set the transparency. + if (0 == SetBkMode(hdc_, TRANSPARENT)) { + NOTREACHED(); + return false; + } + + // Select the NULL brush. + previous_brush_ = SelectObject(GetStockObject(NULL_BRUSH)); + return previous_brush_ != NULL; + } + + // Set the opacity. + if (0 == SetBkMode(hdc_, OPAQUE)) { + NOTREACHED(); + return false; + } + + // Create and select the brush. + previous_brush_ = SelectObject(CreateSolidBrush(color)); + return previous_brush_ != NULL; +} + +bool VectorDevice::CreatePen(bool use_pen, COLORREF color, int stroke_width, + float stroke_miter, DWORD pen_style) { + DCHECK(previous_pen_ == NULL); + // We can't use SetDCPenColor() or DC_PEN when drawing to a EMF buffer. + // SetDCPenColor() calls are not recorded at all and DC_PEN will use BLACK_PEN + // instead. + + // No pen case + if (!use_pen) { + previous_pen_ = SelectObject(GetStockObject(NULL_PEN)); + return previous_pen_ != NULL; + } + + // Use the stock pen if the stroke width is 0. + if (stroke_width == 0) { + // Create a pen with the right color. + previous_pen_ = SelectObject(::CreatePen(PS_SOLID, 0, color)); + return previous_pen_ != NULL; + } + + // Load a custom pen. + LOGBRUSH brush; + brush.lbStyle = BS_SOLID; + brush.lbColor = color; + brush.lbHatch = 0; + HPEN pen = ExtCreatePen(pen_style, stroke_width, &brush, 0, NULL); + DCHECK(pen != NULL); + previous_pen_ = SelectObject(pen); + if (previous_pen_ == NULL) + return false; + + if (!SetMiterLimit(hdc_, stroke_miter, NULL)) { + NOTREACHED(); + return false; + } + return true; +} + +void VectorDevice::Cleanup() { + if (previous_brush_) { + HGDIOBJ result = SelectObject(previous_brush_); + previous_brush_ = NULL; + if (result) { + BOOL res = DeleteObject(result); + DCHECK(res != 0); + } + } + if (previous_pen_) { + HGDIOBJ result = SelectObject(previous_pen_); + previous_pen_ = NULL; + if (result) { + BOOL res = DeleteObject(result); + DCHECK(res != 0); + } + } + // Remove any loaded path from the context. + AbortPath(hdc_); +} + +HGDIOBJ VectorDevice::SelectObject(HGDIOBJ object) { + HGDIOBJ result = ::SelectObject(hdc_, object); + DCHECK(result != HGDI_ERROR); + if (result == HGDI_ERROR) + return NULL; + return result; +} + +bool VectorDevice::CreateBrush(bool use_brush, const SkPaint& paint) { + // Make sure that for transparent color, no brush is used. + if (paint.getAlpha() == 0) { + // Test if it ever happen. + NOTREACHED(); + use_brush = false; + } + + return CreateBrush(use_brush, SkColorToCOLORREF(paint.getColor())); +} + +bool VectorDevice::CreatePen(bool use_pen, const SkPaint& paint) { + // Make sure that for transparent color, no pen is used. + if (paint.getAlpha() == 0) { + // Test if it ever happen. + NOTREACHED(); + use_pen = false; + } + + DWORD pen_style = PS_GEOMETRIC | PS_SOLID; + switch (paint.getStrokeJoin()) { + case SkPaint::kMiter_Join: + // Connects path segments with a sharp join. + pen_style |= PS_JOIN_MITER; + break; + case SkPaint::kRound_Join: + // Connects path segments with a round join. + pen_style |= PS_JOIN_ROUND; + break; + case SkPaint::kBevel_Join: + // Connects path segments with a flat bevel join. + pen_style |= PS_JOIN_BEVEL; + break; + default: + NOTREACHED(); + break; + } + switch (paint.getStrokeCap()) { + case SkPaint::kButt_Cap: + // Begin/end contours with no extension. + pen_style |= PS_ENDCAP_FLAT; + break; + case SkPaint::kRound_Cap: + // Begin/end contours with a semi-circle extension. + pen_style |= PS_ENDCAP_ROUND; + break; + case SkPaint::kSquare_Cap: + // Begin/end contours with a half square extension. + pen_style |= PS_ENDCAP_SQUARE; + break; + default: + NOTREACHED(); + break; + } + + return CreatePen(use_pen, + SkColorToCOLORREF(paint.getColor()), + SkScalarRound(paint.getStrokeWidth()), + paint.getStrokeMiter(), + pen_style); +} + +void VectorDevice::InternalDrawBitmap(const SkBitmap& bitmap, int x, int y, + const SkPaint& paint) { + uint8 alpha = paint.getAlpha(); + if (alpha == 0) + return; + + bool is_translucent; + if (alpha != 255) { + // ApplyPaint expect an opaque color. + SkPaint tmp_paint(paint); + tmp_paint.setAlpha(255); + if (!ApplyPaint(tmp_paint)) + return; + is_translucent = true; + } else { + if (!ApplyPaint(paint)) + return; + is_translucent = false; + } + int src_size_x = bitmap.width(); + int src_size_y = bitmap.height(); + if (!src_size_x || !src_size_y) + return; + + // Create a BMP v4 header that we can serialize. + BITMAPV4HEADER bitmap_header; + gfx::CreateBitmapV4Header(src_size_x, src_size_y, &bitmap_header); + HDC dc = getBitmapDC(); + SkAutoLockPixels lock(bitmap); + DCHECK_EQ(bitmap.getConfig(), SkBitmap::kARGB_8888_Config); + const uint32_t* pixels = static_cast<const uint32_t*>(bitmap.getPixels()); + if (pixels == NULL) { + NOTREACHED(); + return; + } + + if (!is_translucent) { + int row_length = bitmap.rowBytesAsPixels(); + // There is no quick way to determine if an image is opaque. + for (int y2 = 0; y2 < src_size_y; ++y2) { + for (int x2 = 0; x2 < src_size_x; ++x2) { + if (SkColorGetA(pixels[(y2 * row_length) + x2]) != 255) { + is_translucent = true; + y2 = src_size_y; + break; + } + } + } + } + + BITMAPINFOHEADER hdr; + gfx::CreateBitmapHeader(src_size_x, src_size_y, &hdr); + if (is_translucent) { + // The image must be loaded as a bitmap inside a device context. + ScopedHDC bitmap_dc(::CreateCompatibleDC(dc)); + void* bits = NULL; + ScopedBitmap hbitmap(::CreateDIBSection( + bitmap_dc, reinterpret_cast<const BITMAPINFO*>(&hdr), + DIB_RGB_COLORS, &bits, NULL, 0)); + memcpy(bits, pixels, bitmap.getSize()); + DCHECK(hbitmap); + HGDIOBJ old_bitmap = ::SelectObject(bitmap_dc, hbitmap); + DeleteObject(old_bitmap); + + // After some analysis of IE7's behavior, this is the thing to do. I was + // sure IE7 was doing so kind of bitmasking due to the way translucent image + // where renderered but after some windbg tracing, it is being done by the + // printer driver after all (mostly HP printers). IE7 always use AlphaBlend + // for bitmasked images. The trick seems to switch the stretching mode in + // what the driver expects. + DWORD previous_mode = GetStretchBltMode(dc); + BOOL result = SetStretchBltMode(dc, COLORONCOLOR); + DCHECK(result); + // Note that this function expect premultiplied colors (!) + BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; + result = AlphaBlend(dc, + x, y, // Destination origin. + src_size_x, src_size_y, // Destination size. + bitmap_dc, + 0, 0, // Source origin. + src_size_x, src_size_y, // Source size. + blend_function); + DCHECK(result); + result = SetStretchBltMode(dc, previous_mode); + DCHECK(result); + } else { + BOOL result = StretchDIBits(dc, + x, y, // Destination origin. + src_size_x, src_size_y, + 0, 0, // Source origin. + src_size_x, src_size_y, // Source size. + pixels, + reinterpret_cast<const BITMAPINFO*>(&hdr), + DIB_RGB_COLORS, + SRCCOPY); + DCHECK(result); + } + Cleanup(); +} + +} // namespace gfx diff --git a/base/gfx/vector_device.h b/base/gfx/vector_device.h new file mode 100644 index 0000000..002ce17 --- /dev/null +++ b/base/gfx/vector_device.h @@ -0,0 +1,146 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_GFX_VECTOR_DEVICE_H__ +#define BASE_GFX_VECTOR_DEVICE_H__ + +#include "base/basictypes.h" +#include "base/gfx/platform_device.h" +#include "SkMatrix.h" +#include "SkRegion.h" + +namespace gfx { + +// A device is basically a wrapper around SkBitmap that provides a surface for +// SkCanvas to draw into. This specific device is not not backed by a surface +// and is thus unreadable. This is because the backend is completely vectorial. +// This device is a simple wrapper over a Windows device context (HDC) handle. +class VectorDevice : public PlatformDevice { + public: + // Factory function. The DC is kept as the output context. + static VectorDevice* create(HDC dc, int width, int height); + + VectorDevice(HDC dc, const SkBitmap& bitmap); + virtual ~VectorDevice(); + + virtual HDC getBitmapDC() { + return hdc_; + } + + virtual void drawPaint(const SkDraw& draw, const SkPaint& paint); + virtual void drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count, + const SkPoint[], const SkPaint& paint); + virtual void drawRect(const SkDraw& draw, const SkRect& r, + const SkPaint& paint); + virtual void drawPath(const SkDraw& draw, const SkPath& path, + const SkPaint& paint); + virtual void drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, + const SkMatrix& matrix, const SkPaint& paint); + virtual void drawSprite(const SkDraw& draw, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint); + virtual void drawText(const SkDraw& draw, const void* text, size_t len, + SkScalar x, SkScalar y, const SkPaint& paint); + virtual void drawPosText(const SkDraw& draw, const void* text, size_t len, + const SkScalar pos[], SkScalar constY, + int scalarsPerPos, const SkPaint& paint); + virtual void drawTextOnPath(const SkDraw& draw, const void* text, size_t len, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint); + virtual void drawVertices(const SkDraw& draw, SkCanvas::VertexMode, int vertexCount, + const SkPoint verts[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint); + virtual void drawDevice(const SkDraw& draw, SkDevice*, int x, int y, + const SkPaint&); + + + virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region); + virtual void setDeviceOffset(int x, int y); + virtual void drawToHDC(HDC dc, int x, int y, const RECT* src_rect); + virtual bool IsVectorial() { return true; } + + void LoadClipRegion(); + + private: + // Applies the SkPaint's painting properties in the current GDI context, if + // possible. If GDI can't support all paint's properties, returns false. It + // doesn't execute the "commands" in SkPaint. + bool ApplyPaint(const SkPaint& paint); + + // Selects a new object in the device context. It can be a pen, a brush, a + // clipping region, a bitmap or a font. Returns the old selected object. + HGDIOBJ SelectObject(HGDIOBJ object); + + // Creates a brush according to SkPaint's properties. + bool CreateBrush(bool use_brush, const SkPaint& paint); + + // Creates a pen according to SkPaint's properties. + bool CreatePen(bool use_pen, const SkPaint& paint); + + // Restores back the previous objects (pen, brush, etc) after a paint command. + void Cleanup(); + + // Creates a brush according to SkPaint's properties. + bool CreateBrush(bool use_brush, COLORREF color); + + // Creates a pen according to SkPaint's properties. + bool CreatePen(bool use_pen, COLORREF color, int stroke_width, + float stroke_miter, DWORD pen_style); + + // Draws a bitmap in the the device, using the currently loaded matrix. + void InternalDrawBitmap(const SkBitmap& bitmap, int x, int y, + const SkPaint& paint); + + // The Windows Device Context handle. It is the backend used with GDI drawing. + // This backend is write-only and vectorial. + HDC hdc_; + + // Translation assigned to the DC: we need to keep track of this separately + // so it can be updated even if the DC isn't created yet. + SkMatrix transform_; + + // The current clipping + SkRegion clip_region_; + + // Previously selected brush before the current drawing. + HGDIOBJ previous_brush_; + + // Previously selected pen before the current drawing. + HGDIOBJ previous_pen_; + + int offset_x_; + int offset_y_; + + DISALLOW_EVIL_CONSTRUCTORS(VectorDevice); +}; + +} // namespace gfx + +#endif // BASE_GFX_VECTOR_DEVICE_H__ diff --git a/base/hash_tables.h b/base/hash_tables.h new file mode 100644 index 0000000..13c201b --- /dev/null +++ b/base/hash_tables.h @@ -0,0 +1,54 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Deal with the differences between Microsoft and GNU implemenations +// of hash_map +// + +#ifndef BASE_HASH_TABLES_H__ +#define BASE_HASH_TABLES_H__ + +#ifdef WIN32 +#include <hash_map> +#include <hash_set> +namespace base { + using stdext::hash_map; + using stdext::hash_set; +} +#else +#include <ext/hash_map> +#include <ext/hash_set> +namespace base { + using __gnu_cxx::hash_map; + using __gnu_cxx::hash_set; +} + +#endif + +#endif // BASE_HASH_TABLES_H__ diff --git a/base/histogram.cc b/base/histogram.cc new file mode 100644 index 0000000..fc72f8f --- /dev/null +++ b/base/histogram.cc @@ -0,0 +1,668 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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/histogram.h" + +#include <math.h> +#include <string> + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" + +typedef Histogram::Count Count; + +Histogram::Histogram(const wchar_t* name, Sample minimum, + Sample maximum, size_t bucket_count) + : StatsRate(name), + histogram_name_(WideToASCII(name)), + declared_min_(minimum), + declared_max_(maximum), + bucket_count_(bucket_count), + flags_(0), + ranges_(bucket_count + 1, 0), + sample_(), + registered_(false) { + Initialize(); +} + +Histogram::Histogram(const wchar_t* name, TimeDelta minimum, + TimeDelta maximum, size_t bucket_count) + : StatsRate(name), + histogram_name_(WideToASCII(name)), + declared_min_(static_cast<int> (minimum.InMilliseconds())), + declared_max_(static_cast<int> (maximum.InMilliseconds())), + bucket_count_(bucket_count), + flags_(0), + ranges_(bucket_count + 1, 0), + sample_(), + registered_(false) { + Initialize(); +} + +Histogram::~Histogram() { + if (registered_) + StatisticsRecorder::UnRegister(*this); + // Just to make sure most derived class did this properly... + DCHECK(ValidateBucketRanges()); +} + + +// Hooks to override stats counter methods. This ensures that we gather all +// input the stats counter sees. +void Histogram::Add(int value) { + if (!registered_) + registered_ = StatisticsRecorder::Register(*this); + if (value >= kSampleType_MAX) + value = kSampleType_MAX - 1; + StatsRate::Add(value); + if (value < 0) + value = 0; + size_t index = BucketIndex(value); + DCHECK(value >= ranges(index)); + DCHECK(value < ranges(index + 1)); + Accumulate(value, 1, index); +} + +// The following methods provide a graphical histogram display. +void Histogram::WriteHTMLGraph(std::string* output) const { + // TBD(jar) Write a nice HTML bar chart, with divs an mouse-overs etc. + output->append("<PRE>"); + WriteAscii(true, "<br>", output); + output->append("</PRE>"); +} + +void Histogram::WriteAscii(bool graph_it, const std::string& newline, + std::string* output) const { + // Get local (stack) copies of all effectively volatile class data so that we + // are consistent across our output activities. + SampleSet snapshot; + SnapshotSample(&snapshot); + 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 == sample_.counts(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.counts(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.counts(i); + if (!current && !PrintEmptyBucket(i)) + continue; + remaining -= current; + StringAppendF(output, "%#*s ", print_width, GetAsciiBucketRange(i).c_str()); + if (0 == current && i < bucket_count_ - 1 && 0 == snapshot.counts(i + 1)) { + while (i < bucket_count_ - 1 && 0 == snapshot.counts(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(past == sample_count); +} + +bool Histogram::ValidateBucketRanges() const { + // Standard assertions that all bucket ranges should satisfy. + DCHECK(ranges_.size() == bucket_count_ + 1); + DCHECK(0 == ranges_[0]); + DCHECK(declared_min() == ranges_[1]); + DCHECK(declared_max() == ranges_[bucket_count_ - 1]); + DCHECK(kSampleType_MAX == ranges_[bucket_count_]); + return true; +} + +void Histogram::Initialize() { + sample_.Resize(*this); + if (declared_min_ <= 0) + declared_min_ = 1; + if (declared_max_ >= kSampleType_MAX) + declared_max_ = kSampleType_MAX - 1; + DCHECK(declared_min_ > 0); // We provide underflow bucket. + DCHECK(declared_min_ < declared_max_); + DCHECK(1 < bucket_count_); + size_t maximal_bucket_count = declared_max_ - declared_min_ + 2; + DCHECK(bucket_count_ <= maximal_bucket_count); + DCHECK(0 == ranges_[0]); + ranges_[bucket_count_] = kSampleType_MAX; + InitializeBucketRange(); + DCHECK(ValidateBucketRanges()); + registered_ = StatisticsRecorder::Register(*this); +} + +// 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. +void Histogram::InitializeBucketRange() { + double log_max = log(static_cast<double>(declared_max())); + double log_ratio; + double log_next; + size_t bucket_index = 1; + Sample current = declared_min(); + SetBucketRange(bucket_index, current); + while (bucket_count() > ++bucket_index) { + double log_current; + log_current = log(static_cast<double>(current)); + // Calculate the count'th root of the range. + log_ratio = (log_max - log_current) / (bucket_count() - bucket_index); + // See where the next bucket would start. + log_next = log_current + log_ratio; + int next; + next = static_cast<int>(floor(exp(log_next) + 0.5)); + if (next > current) + current = next; + else + current++; // Just do a narrow bucket, and keep trying. + SetBucketRange(bucket_index, current); + } + + DCHECK(bucket_count() == bucket_index); +} + +size_t Histogram::BucketIndex(Sample value) const { + // Use simple binary search. This is very general, but there are better + // approaches if we knew that the buckets were linearly distributed. + DCHECK(ranges(0) <= value); + DCHECK(ranges(bucket_count()) > value); + size_t under = 0; + size_t over = bucket_count(); + size_t mid; + + do { + DCHECK(over >= under); + mid = (over + under)/2; + if (mid == under) + break; + if (ranges(mid) <= value) + under = mid; + else + over = mid; + } while (true); + + DCHECK(ranges(mid) <= value && ranges(mid+1) > value); + return mid; +} + +// 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(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; +} + +//------------------------------------------------------------------------------ +// The following two methods can be overridden to provide a thread safe +// version of this class. The cost of locking is low... but an error in each +// of these methods has minimal impact. For now, I'll leave this unlocked, +// and I don't believe I can loose more than a count or two. +// The vectors are NOT reallocated, so there is no risk of them moving around. + +// Update histogram data with new sample. +void Histogram::Accumulate(Sample value, Count count, size_t index) { + // Note locking not done in this version!!! + sample_.Accumulate(value, count, index); +} + +// Do a safe atomic snapshot of sample data. +// This implementation assumes we are on a safe single thread. +void Histogram::SnapshotSample(SampleSet* sample) const { + // Note locking not done in this version!!! + *sample = sample_; +} + +//------------------------------------------------------------------------------ +// Accessor methods + +void Histogram::SetBucketRange(size_t i, Sample value) { + DCHECK(bucket_count_ > i); + ranges_[i] = value; +} + +//------------------------------------------------------------------------------ +// Private methods + +double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const { + double max = 0; + for (size_t i = 0; i < bucket_count_ ; i++) { + double current_size = GetBucketSize(snapshot.counts(i), i); + if (current_size > max) + max = current_size; + } + return max; +} + +void Histogram::WriteAsciiHeader(const SampleSet& snapshot, + Count sample_count, + std::string* output) const { + StringAppendF(output, + "Histogram: %s recorded %ld samples", + histogram_name().c_str(), + sample_count); + if (0 == sample_count) { + DCHECK(0 == snapshot.sum()); + } else { + double average = static_cast<float>(snapshot.sum()) / sample_count; + double variance = static_cast<float>(snapshot.square_sum())/sample_count + - average * average; + double standard_deviation = sqrt(variance); + + StringAppendF(output, + ", average = %.1f, standard deviation = %.1f", + average, standard_deviation); + } + 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, + std::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); + } +} + +const std::string Histogram::GetAsciiBucketRange(size_t i) const { + std::string result; + if (kHexRangePrintingFlag & flags_) + StringAppendF(&result, "%#x", ranges_[i]); + else + StringAppendF(&result, "%d", ranges_[i]); + return result; +} + +void Histogram::WriteAsciiBucketValue(Count current, double scaled_sum, + std::string* output) const { + StringAppendF(output, " (%d = %3.1f%%)", current, current/scaled_sum); +} + +void Histogram::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<int>(k_line_length * (current_size / max_size) + + 0.5); + int x_remainder = k_line_length - x_count; + + while (0 < x_count--) + output->append("-"); + output->append("O"); + while (0 < x_remainder--) + output->append(" "); +} + +//------------------------------------------------------------------------------ +// Methods for the Histogram::SampleSet class +//------------------------------------------------------------------------------ + +Histogram::SampleSet::SampleSet() + : counts_(), + sum_(0), + square_sum_(0) { +} + +void Histogram::SampleSet::Resize(const Histogram& histogram) { + counts_.resize(histogram.bucket_count(), 0); +} + +void Histogram::SampleSet::CheckSize(const Histogram& histogram) const { + DCHECK(counts_.size() == histogram.bucket_count()); +} + + +void Histogram::SampleSet::Accumulate(Sample value, Count count, + size_t index) { + DCHECK(count == 1 || count == -1); + counts_[index] += count; + sum_ += count * value; + square_sum_ += (count * value) * static_cast<int64>(value); + DCHECK(counts_[index] >= 0); + DCHECK(sum_ >= 0); + DCHECK(square_sum_ >= 0); +} + +Count Histogram::SampleSet::TotalCount() const { + Count total = 0; + for (Counts::const_iterator it = counts_.begin(); + it != counts_.end(); + it++) { + total += *it; + } + return total; +} + +void Histogram::SampleSet::Add(const SampleSet& other) { + DCHECK(counts_.size() == other.counts_.size()); + sum_ += other.sum_; + square_sum_ += other.square_sum_; + for (size_t index = 0; index < counts_.size(); index++) + counts_[index] += other.counts_[index]; +} + +void Histogram::SampleSet::Subtract(const SampleSet& other) { + DCHECK(counts_.size() == other.counts_.size()); + // Note: Race conditions in snapshotting a sum or square_sum may lead to + // (temporary) negative values when snapshots are later combined (and deltas + // calculated). As a result, we don't currently CHCEK() for positive values. + sum_ -= other.sum_; + square_sum_ -= other.square_sum_; + for (size_t index = 0; index < counts_.size(); index++) { + counts_[index] -= other.counts_[index]; + DCHECK(counts_[index] >= 0); + } +} + +//------------------------------------------------------------------------------ +// LinearHistogram: This histogram uses a traditional set of evenly spaced +// buckets. +//------------------------------------------------------------------------------ + +LinearHistogram::LinearHistogram(const wchar_t* name, + Sample minimum, Sample maximum, size_t bucket_count) + : Histogram(name, minimum >= 1 ? minimum : 1, maximum, bucket_count) { + InitializeBucketRange(); + DCHECK(ValidateBucketRanges()); +} + +LinearHistogram::LinearHistogram(const wchar_t* name, + TimeDelta minimum, TimeDelta maximum, size_t bucket_count) + : Histogram(name, minimum >= TimeDelta::FromMilliseconds(1) ? + minimum : TimeDelta::FromMilliseconds(1), + maximum, bucket_count) { + // Do a "better" (different) job at init than a base classes did... + InitializeBucketRange(); + DCHECK(ValidateBucketRanges()); +} + +void LinearHistogram::SetRangeDescriptions(const DescriptionPair descriptions[]) { + for (int i =0; descriptions[i].description; ++i) { + bucket_description_[descriptions[i].sample] = descriptions[i].description; + } +} + +const std::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(); +} + + +void LinearHistogram::InitializeBucketRange() { + DCHECK(0 < declared_min()); // 0 is the underflow bucket here. + double min = declared_min(); + double max = declared_max(); + size_t i; + for (i = 1; i < bucket_count(); i++) { + double linear_range = (min * (bucket_count() -1 - i) + max * (i - 1)) / + (bucket_count() - 2); + SetBucketRange(i, static_cast<int> (linear_range + 0.5)); + } +} + +// Find bucket to increment for sample value. +size_t LinearHistogram::BucketIndex(Sample value) const { + if (value < declared_min()) return 0; + if (value >= declared_max()) return bucket_count() - 1; + size_t index; + index = static_cast<size_t>(((value - declared_min()) * (bucket_count() - 2)) + / (declared_max() - declared_min()) + 1); + DCHECK(1 <= index && bucket_count() > index); + return index; +} + +double LinearHistogram::GetBucketSize(Count current, size_t i) const { + DCHECK(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; +} + +//------------------------------------------------------------------------------ +// This section provides implementation for ThreadSafeHistogram. +//------------------------------------------------------------------------------ + +ThreadSafeHistogram::ThreadSafeHistogram(const wchar_t* name, Sample minimum, + Sample maximum, size_t bucket_count) + : Histogram(name, minimum, maximum, bucket_count), + lock_() { + } + +void ThreadSafeHistogram::Remove(int value) { + if (value >= kSampleType_MAX) + value = kSampleType_MAX - 1; + StatsRate::Add(-value); + size_t index = BucketIndex(value); + Accumulate(value, -1, index); +} + +void ThreadSafeHistogram::Accumulate(Sample value, Count count, size_t index) { + AutoLock lock(lock_); + Histogram::Accumulate(value, count, index); +} + +void ThreadSafeHistogram::SnapshotSample(SampleSet* sample) { + AutoLock lock(lock_); + Histogram::SnapshotSample(sample); +}; + + +//------------------------------------------------------------------------------ +// The next section handles global (central) support for all histograms, as well +// as startup/teardown of this service. +//------------------------------------------------------------------------------ + +// 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_); + lock_ = new Lock; + histograms_ = new HistogramMap; +} + +StatisticsRecorder::~StatisticsRecorder() { + DCHECK(histograms_); + + if (dump_on_exit_) { + std::string output; + WriteGraph("", &output); + LOG(INFO) << output; + } + + // Clean up. + delete histograms_; + histograms_ = NULL; + delete lock_; + lock_ = NULL; +} + +// static +bool StatisticsRecorder::WasStarted() { + return NULL != histograms_; +} + +// static +bool StatisticsRecorder::Register(const Histogram& histogram) { + if (!histograms_) + return false; + const std::string name = histogram.histogram_name(); + AutoLock auto_lock(*lock_); + DCHECK(histograms_->end() == histograms_->find(name)); // Only register once. + (*histograms_)[name] = &histogram; + return true; +} + +// static +void StatisticsRecorder::UnRegister(const Histogram& histogram) { + if (!histograms_) + return; + const std::string name = histogram.histogram_name(); + AutoLock auto_lock(*lock_); + DCHECK(histograms_->end() != histograms_->find(name)); + histograms_->erase(name); + if (dump_on_exit_) { + std::string output; + histogram.WriteAscii(true, "\n", &output); + LOG(INFO) << output; + } +} + +// static +void StatisticsRecorder::WriteHTMLGraph(const std::string& query, + std::string* output) { + if (!histograms_) + return; + output->append("<html><head><title>About Histograms"); + if (!query.empty()) + output->append(" - " + query); + output->append("</title>" + // We'd like the following no-cache... but it doesn't work. + // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">" + "</head><body>"); + + Histograms snapshot; + GetSnapshot(query, &snapshot); + for (Histograms::iterator it = snapshot.begin(); + it != snapshot.end(); + it++) { + (*it)->WriteHTMLGraph(output); + output->append("<br><hr><br>"); + } + output->append("</body></html>"); +} + +// static +void StatisticsRecorder::WriteGraph(const std::string& query, + std::string* output) { + if (!histograms_) + 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(true, "\n", output); + output->append("\n"); + } +} + +// static +void StatisticsRecorder::GetHistograms(Histograms* output) { + if (!histograms_) + return; + AutoLock auto_lock(*lock_); + for (HistogramMap::iterator it = histograms_->begin(); + histograms_->end() != it; + it++) { + output->push_back(it->second); + } +} + +// private static +void StatisticsRecorder::GetSnapshot(const std::string& query, + Histograms* snapshot) { + AutoLock auto_lock(*lock_); + for (HistogramMap::iterator it = histograms_->begin(); + histograms_->end() != it; + it++) { + if (it->first.find(query) != std::string::npos) + snapshot->push_back(it->second); + } +} + +// static +StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL; +// static +Lock* StatisticsRecorder::lock_ = NULL; +// static +bool StatisticsRecorder::dump_on_exit_ = false; diff --git a/base/histogram.h b/base/histogram.h new file mode 100644 index 0000000..295f610 --- /dev/null +++ b/base/histogram.h @@ -0,0 +1,469 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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. + +// The default layout of buckets 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(L"some name", 1, 64, 8); +// Note that the underflow bucket [0,1) and the overflow bucket [64,infinity) +// are not 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. + +#ifndef BASE_HISTOGRAM_H__ +#define BASE_HISTOGRAM_H__ + +#include <map> +#include <string> +#include <vector> + +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/stats_counters.h" + +//------------------------------------------------------------------------------ +// Provide easy general purpose histogram in a macro, just like stats counters. +// These macros all use 50 buckets. + +#define HISTOGRAM_TIMES(name, sample) do { \ + static Histogram counter((name), TimeDelta::FromMilliseconds(1), \ + TimeDelta::FromSeconds(10), 50); \ + counter.AddTime(sample); \ + } while (0) + +#define HISTOGRAM_COUNTS(name, sample) do { \ + static Histogram counter((name), 1, 1000000, 50); \ + counter.Add(sample); \ + } while (0) + +//------------------------------------------------------------------------------ +// This macro set is for a histogram that can support both addition and removal +// of samples. It should be used to render the accumulated asset allocation +// of some samples. For example, it can sample memory allocation sizes, and +// memory releases (as negative samples). +// To simplify the interface, only non-zero values can be sampled, with positive +// numbers indicating addition, and negative numbers implying dimunition +// (removal). +// Note that the underlying ThreadSafeHistogram() uses locking to ensure that +// counts are precise (no chance of losing an addition or removal event, due to +// multithread racing). This precision is required to prevent missed-counts from +// resulting in drift, as the calls to Remove() for a given value should always +// be equal in number or fewer than the corresponding calls to Add(). + +#define ASSET_HISTOGRAM_COUNTS(name, sample) do { \ + static ThreadSafeHistogram counter((name), 1, 1000000, 50); \ + if (0 == sample) break; \ + if (sample >= 0) \ + counter.Add(sample); \ + else\ + counter.Remove(-sample); \ + } while (0) + +//------------------------------------------------------------------------------ +// 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 DASSET_HISTOGRAM_COUNTS(name, sample) ASSET_HISTOGRAM_COUNTS(name, \ + sample) + +#else // NDEBUG + +#define DHISTOGRAM_TIMES(name, sample) do {} while (0) +#define DHISTOGRAM_COUNTS(name, sample) do {} while (0) +#define DASSET_HISTOGRAM_COUNTS(name, sample) do {} while (0) + +#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. + +static const int kUmaTargetedHistogramFlag = 0x1; + +#define UMA_HISTOGRAM_TIMES(name, sample) do { \ + static Histogram counter((name), TimeDelta::FromMilliseconds(1), \ + TimeDelta::FromSeconds(10), 50); \ + counter.SetFlags(kUmaTargetedHistogramFlag); \ + counter.AddTime(sample); \ + } while (0) + +// Use this macro when times can routinely be much longer than 10 seconds. +#define UMA_HISTOGRAM_LONG_TIMES(name, sample) do { \ + static Histogram counter((name), TimeDelta::FromMilliseconds(1), \ + TimeDelta::FromHours(1), 50); \ + counter.SetFlags(kUmaTargetedHistogramFlag); \ + counter.AddTime(sample); \ + } while (0) + +#define UMA_HISTOGRAM_COUNTS(name, sample) do { \ + static Histogram counter((name), 1, 1000000, 50); \ + counter.SetFlags(kUmaTargetedHistogramFlag); \ + counter.Add(sample); \ + } while (0) + +#define UMA_HISTOGRAM_COUNTS_100(name, sample) do { \ + static Histogram counter((name), 1, 100, 50); \ + counter.SetFlags(kUmaTargetedHistogramFlag); \ + counter.Add(sample); \ + } while (0) + +#define UMA_HISTOGRAM_MEMORY_KB(name, sample) do { \ + static Histogram counter((name), 1000, 500000, 50); \ + counter.SetFlags(kUmaTargetedHistogramFlag); \ + counter.Add(sample); \ + } while (0) + +#define UMA_HISTOGRAM_MEMORY_MB(name, sample) do { \ + static Histogram counter((name), 1, 1000, 50); \ + counter.SetFlags(kUmaTargetedHistogramFlag); \ + counter.Add(sample); \ + } while (0) + +//------------------------------------------------------------------------------ + +class Histogram : public StatsRate { + public: + typedef int Sample; // Used for samples (and ranges of samples). + typedef int Count; // Used to count samples in a bucket. + static const Sample kSampleType_MAX = INT_MAX; + + typedef std::vector<Count> Counts; + typedef std::vector<const Sample> Ranges; + + static const int kHexRangePrintingFlag = 0x8000; + //---------------------------------------------------------------------------- + // Statistic values, developed over the life of the histogram. + + class SampleSet { + public: + explicit SampleSet(); + // Adjust size of counts_ for use with given histogram. + void Resize(const Histogram& histogram); + void CheckSize(const Histogram& histogram) const; + + // Accessor for histogram to make routine additions. + void Accumulate(Sample value, Count count, size_t index); + + // Accessor methods. + Count counts(size_t i) const { return counts_[i]; } + Count TotalCount() const ; + int64 sum() const { return sum_; } + int64 square_sum() const { return square_sum_; } + + // Arithmetic manipulation of corresponding elements of the set. + void Add(const SampleSet& other); + void Subtract(const SampleSet& other); + + private: + // Actual histogram data is stored in buckets, showing the count of values + // that fit into each bucket. + Counts counts_; + + // Save simple stats locally. Note that this MIGHT get done in base class + // without shared memory at some point. + int64 sum_; // sum of samples. + int64 square_sum_; // sum of squares of samples. + }; + //---------------------------------------------------------------------------- + + Histogram(const wchar_t* name, Sample minimum, + Sample maximum, size_t bucket_count); + Histogram(const wchar_t* name, TimeDelta minimum, + TimeDelta maximum, size_t bucket_count); + ~Histogram(); + + // Hooks to override stats counter methods. This ensures that we gather all + // input the stats counter sees. + virtual void Add(int value); + + // The following methods provide a graphical histogram displays. + void WriteHTMLGraph(std::string* output) const; + void WriteAscii(bool graph_it, const std::string& newline, + std::string* output) const; + + // Support generic flagging of Histograms. + // 0x1 Currently used to mark this histogram to be recorded by UMA.. + // 0x8000 means print ranges in hex. + void SetFlags(int flags) { flags_ |= flags; } + int flags() const { return flags_; } + + //---------------------------------------------------------------------------- + // Accessors for serialization and testing. + //---------------------------------------------------------------------------- + const std::string histogram_name() const { return histogram_name_; } + Sample declared_min() const { return declared_min_; } + Sample declared_max() const { return declared_max_; } + Sample ranges(size_t i) const { return ranges_[i];} + size_t bucket_count() const { return bucket_count_; } + // Snapshot the current complete set of sample data. + // Override with atomic/locked snapshot if needed. + virtual void SnapshotSample(SampleSet* sample) const; + + protected: + // Method to override to skip the display of the i'th bucket if it's empty. + virtual bool PrintEmptyBucket(size_t index) const { return true; } + + //---------------------------------------------------------------------------- + // Methods to override to create histogram with different bucket widths. + //---------------------------------------------------------------------------- + // Initialize ranges_ mapping. + virtual void InitializeBucketRange(); + // Find bucket to increment for sample value. + virtual size_t BucketIndex(Sample value) 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; + + //---------------------------------------------------------------------------- + // Methods to override to create thread safe histogram. + //---------------------------------------------------------------------------- + // Update all our internal data, including histogram + virtual void Accumulate(Sample value, Count count, size_t index); + + //---------------------------------------------------------------------------- + // Accessors for derived classes. + //---------------------------------------------------------------------------- + void SetBucketRange(size_t i, Sample value); + + // Validate that ranges_ was created sensibly (top and bottom range + // values relate properly to the declared_min_ and declared_max_).. + bool ValidateBucketRanges() const; + + private: + // Post constructor initialization. + void Initialize(); + + //---------------------------------------------------------------------------- + // Helpers for emitting Ascii graphic. Each method appends data to output. + + // Find out how large the (graphically) the largest bucket will appear to be. + double GetPeakBucketSize(const SampleSet& snapshot) const; + + // Write a common header message describing this histogram. + void WriteAsciiHeader(const SampleSet& snapshot, + Count sample_count, std::string* output) const ; + + // Write information about previous, current, and next buckets. + // Information such as cumulative percentage, etc. + void WriteAsciiBucketContext(const int64 past, const Count current, + const int64 remaining, const size_t i, + std::string* output) const; + + // Write textual description of the bucket contents (relative to histogram). + // Output is the count in the buckets, as well as the percentage. + void WriteAsciiBucketValue(Count current, double scaled_sum, + std::string* output) const; + + // Produce actual graph (set of blank vs non blank char's) for a bucket. + void WriteAsciiBucketGraph(double current_size, double max_size, + std::string* output) const; + + //---------------------------------------------------------------------------- + // Invariant values set at/near construction time + + // ASCII version of original name given to the constructor. All identically + // named instances will be coalesced cross-project TODO(jar). + // If a user needs one histogram name to be called by several places in a + // single process, a central function should be defined by teh user, which + // defins the single declared instance of the named histogram. + const std::string histogram_name_; + Sample declared_min_; // Less than this goes into counts_[0] + Sample declared_max_; // Over this goes into counts_[bucket_count_ - 1]. + size_t bucket_count_; // Dimension of counts_[]. + + // Flag the histogram for recording by UMA via metric_services.h. + int flags_; + + // For each index, show the least value that can be stored in the + // corresponding bucket. We also append one extra element in this array, + // containing kSampleType_MAX, to make calculations easy. + // The dimension of ranges_ is bucket_count + 1. + Ranges ranges_; + + // Finally, provide the state that changes with the addition of each new + // sample. + SampleSet sample_; + + // Indicate if successfully registered. + bool registered_; + + DISALLOW_EVIL_CONSTRUCTORS(Histogram); +}; + +//------------------------------------------------------------------------------ + +// LinearHistogram is a more traditional histogram, with evenly spaced +// buckets. +class LinearHistogram : public Histogram { + public: + struct DescriptionPair { + Sample sample; + char* description; // Null means end of a list of pairs. + }; + LinearHistogram(const wchar_t* name, Sample minimum, + Sample maximum, size_t bucket_count); + LinearHistogram(const wchar_t* name, TimeDelta minimum, + TimeDelta maximum, size_t bucket_count); + ~LinearHistogram() {} + + // Store a list of number/text values for use in rendering the histogram. + // The last element in the array has a null in its "description" slot. + void SetRangeDescriptions(const DescriptionPair descriptions[]); + + protected: + // Initialize ranges_ mapping. + virtual void InitializeBucketRange(); + // Find bucket to increment for sample value. + virtual size_t BucketIndex(Sample value) const; + virtual double LinearHistogram::GetBucketSize(Count current, + size_t i) const; + + // 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; + + // 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; + + private: + // For some ranges, we store a printable description of a bucket range. + // If there is no desciption, then GetAsciiBucketRange() uses parent class + // to provide a description. + typedef std::map<Sample, std::string> BucketDescriptionMap; + BucketDescriptionMap bucket_description_; + + DISALLOW_EVIL_CONSTRUCTORS(LinearHistogram); +}; + + +//------------------------------------------------------------------------------ +// This section provides implementation for ThreadSafeHistogram. +//------------------------------------------------------------------------------ + +class ThreadSafeHistogram : public Histogram { + public: + ThreadSafeHistogram(const wchar_t* name, Sample minimum, + Sample maximum, size_t bucket_count); + + // Provide the analog to Add() + void Remove(int value); + + protected: + // Provide locked versions to get precise counts. + virtual void Accumulate(Sample value, Count count, size_t index); + + virtual void SnapshotSample(SampleSet* sample); + + private: + Lock lock_; + + DISALLOW_EVIL_CONSTRUCTORS(ThreadSafeHistogram); +}; + +//------------------------------------------------------------------------------ +// StatisticsRecorder handles all histograms in the system. It provides a +// general place for histograms to register, and supports a global API for +// accessing (i.e., dumping, or graphing) the data in all the histograms. + +class StatisticsRecorder { + public: + typedef std::vector<const Histogram*> Histograms; + + StatisticsRecorder(); + + ~StatisticsRecorder(); + + // Find out if histograms can now be registered into our list. + static bool WasStarted(); + + // Register, or add a new histogram to the collection of statistics. + // Return true if registered. + static bool Register(const Histogram& histogram); + // Unregister, or remove, a histogram from the collection of statistics. + static void UnRegister(const Histogram& histogram); + + // 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); + + static void set_dump_on_exit(bool enable) { dump_on_exit_ = enable; } + + private: + typedef std::map<std::string, const Histogram*> HistogramMap; + // We keep all registered histograms in a map, from name to histogram. + + // 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); + + static HistogramMap* histograms_; + // lock protects access to the above map. + static Lock* lock_; + + // Dump all known histograms to log. + static bool dump_on_exit_; + + DISALLOW_EVIL_CONSTRUCTORS(StatisticsRecorder); +}; + +#endif // BASE_HISTOGRAM_H__ + diff --git a/base/histogram_test.cc b/base/histogram_test.cc new file mode 100644 index 0000000..9830392 --- /dev/null +++ b/base/histogram_test.cc @@ -0,0 +1,319 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Test of Histogram class + +#include "base/histogram.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class HistogramTest : public testing::Test { +}; + +// Check for basic syntax and use. +TEST(HistogramTest, StartupShutdownTest) { + // Try basic construction + Histogram histogram(L"TestHistogram", 1, 1000, 10); + Histogram histogram1(L"Test1Histogram", 1, 1000, 10); + + LinearHistogram linear_histogram(L"TestLinearHistogram", 1, 1000, 10); + LinearHistogram linear_histogram1(L"Test1LinearHistogram", 1, 1000, 10); + + // Use standard macros (but with fixed samples) + HISTOGRAM_TIMES(L"Test2Histogram", TimeDelta::FromDays(1)); + HISTOGRAM_COUNTS(L"Test3Histogram", 30); + + DHISTOGRAM_TIMES(L"Test4Histogram", TimeDelta::FromDays(1)); + DHISTOGRAM_COUNTS(L"Test5Histogram", 30); + + ASSET_HISTOGRAM_COUNTS(L"Test6Histogram", 129); + + // Try to construct samples. + Histogram::SampleSet sample1; + Histogram::SampleSet sample2; + + // Use copy constructor of SampleSet + sample1 = sample2; + Histogram::SampleSet sample3(sample1); + + // Finally test a statistics recorder, without really using it. + StatisticsRecorder recorder; +} + +// Repeat with a recorder present to register with. +TEST(HistogramTest, RecordedStartupTest) { + // Test a statistics recorder, by letting histograms register. + StatisticsRecorder recorder; // This initializes the global state. + + StatisticsRecorder::Histograms histograms; + EXPECT_EQ(0, histograms.size()); + StatisticsRecorder::GetHistograms(&histograms); // Load up lists + EXPECT_EQ(0, histograms.size()); + + // Try basic construction + Histogram histogram(L"TestHistogram", 1, 1000, 10); + histograms.clear(); + StatisticsRecorder::GetHistograms(&histograms); // Load up lists + EXPECT_EQ(1, histograms.size()); + Histogram histogram1(L"Test1Histogram", 1, 1000, 10); + histograms.clear(); + StatisticsRecorder::GetHistograms(&histograms); // Load up lists + EXPECT_EQ(2, histograms.size()); + + LinearHistogram linear_histogram(L"TestLinearHistogram", 1, 1000, 10); + LinearHistogram linear_histogram1(L"Test1LinearHistogram", 1, 1000, 10); + histograms.clear(); + StatisticsRecorder::GetHistograms(&histograms); // Load up lists + EXPECT_EQ(4, histograms.size()); + + // Use standard macros (but with fixed samples) + HISTOGRAM_TIMES(L"Test2Histogram", TimeDelta::FromDays(1)); + HISTOGRAM_COUNTS(L"Test3Histogram", 30); + histograms.clear(); + StatisticsRecorder::GetHistograms(&histograms); // Load up lists + EXPECT_EQ(6, histograms.size()); + + ASSET_HISTOGRAM_COUNTS(L"TestAssetHistogram", 1000); + histograms.clear(); + StatisticsRecorder::GetHistograms(&histograms); // Load up lists + EXPECT_EQ(7, histograms.size()); + + DHISTOGRAM_TIMES(L"Test4Histogram", TimeDelta::FromDays(1)); + DHISTOGRAM_COUNTS(L"Test5Histogram", 30); + histograms.clear(); + StatisticsRecorder::GetHistograms(&histograms); // Load up lists +#ifndef NDEBUG + EXPECT_EQ(9, histograms.size()); +#else + EXPECT_EQ(7, histograms.size()); +#endif +} + +TEST(HistogramTest, RangeTest) { + StatisticsRecorder recorder; + StatisticsRecorder::Histograms histograms; + + recorder.GetHistograms(&histograms); + EXPECT_EQ(0, histograms.size()); + + Histogram histogram(L"Histogram", 1, 64, 8); // As mentioned in header file. + // Check that we got a nice exponential when there was enough rooom. + EXPECT_EQ(0, histogram.ranges(0)); + int power_of_2 = 1; + for (int i = 1; i < 8; i++) { + EXPECT_EQ(power_of_2, histogram.ranges(i)); + power_of_2 *= 2; + } + EXPECT_EQ(INT_MAX, histogram.ranges(8)); + + Histogram short_histogram(L"Histogram Shortened", 1, 7, 8); + // Check that when the number of buckets is short, we get a linear histogram + // for lack of space to do otherwise. + for (int i = 0; i < 8; i++) + EXPECT_EQ(i, short_histogram.ranges(i)); + EXPECT_EQ(INT_MAX, short_histogram.ranges(8)); + + LinearHistogram linear_histogram(L"Linear", 1, 7, 8); + // We also get a nice linear set of bucket ranges when we ask for it + for (int i = 0; i < 8; i++) + EXPECT_EQ(i, linear_histogram.ranges(i)); + EXPECT_EQ(INT_MAX, linear_histogram.ranges(8)); + + LinearHistogram linear_broad_histogram(L"Linear widened", 2, 14, 8); + // ...but when the list has more space, then the ranges naturally spread out. + for (int i = 0; i < 8; i++) + EXPECT_EQ(2 * i, linear_broad_histogram.ranges(i)); + EXPECT_EQ(INT_MAX, linear_broad_histogram.ranges(8)); + + ThreadSafeHistogram threadsafe_histogram(L"ThreadSafe", 1, 32, 15); + // When space is a little tight, we transition from linear to exponential. + // This is what happens in both the basic histogram, and the threadsafe + // variant (which is derived). + EXPECT_EQ(0, threadsafe_histogram.ranges(0)); + EXPECT_EQ(1, threadsafe_histogram.ranges(1)); + EXPECT_EQ(2, threadsafe_histogram.ranges(2)); + EXPECT_EQ(3, threadsafe_histogram.ranges(3)); + EXPECT_EQ(4, threadsafe_histogram.ranges(4)); + EXPECT_EQ(5, threadsafe_histogram.ranges(5)); + EXPECT_EQ(6, threadsafe_histogram.ranges(6)); + EXPECT_EQ(7, threadsafe_histogram.ranges(7)); + EXPECT_EQ(9, threadsafe_histogram.ranges(8)); + EXPECT_EQ(11, threadsafe_histogram.ranges(9)); + EXPECT_EQ(14, threadsafe_histogram.ranges(10)); + EXPECT_EQ(17, threadsafe_histogram.ranges(11)); + EXPECT_EQ(21, threadsafe_histogram.ranges(12)); + EXPECT_EQ(26, threadsafe_histogram.ranges(13)); + EXPECT_EQ(32, threadsafe_histogram.ranges(14)); + EXPECT_EQ(INT_MAX, threadsafe_histogram.ranges(15)); + + recorder.GetHistograms(&histograms); + EXPECT_EQ(5, histograms.size()); +} + +// Make sure histogram handles out-of-bounds data gracefully. +TEST(HistogramTest, BoundsTest) { + const int kBucketCount = 50; + Histogram histogram(L"Bounded", 10, 100, kBucketCount); + + // 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. + Histogram::SampleSet sample; + histogram.SnapshotSample(&sample); + EXPECT_EQ(2, sample.counts(0)); + EXPECT_EQ(0, sample.counts(1)); + size_t array_size = histogram.bucket_count(); + EXPECT_EQ(kBucketCount, array_size); + EXPECT_EQ(0, sample.counts(array_size - 2)); + EXPECT_EQ(2, sample.counts(array_size - 1)); +} + +// Check to be sure samples land as expected is "correct" buckets. +TEST(HistogramTest, BucketPlacementTest) { + Histogram histogram(L"Histogram", 1, 64, 8); // As mentioned in header file. + + // Check that we got a nice exponential since there was enough rooom. + EXPECT_EQ(0, histogram.ranges(0)); + int power_of_2 = 1; + for (int i = 1; i < 8; i++) { + EXPECT_EQ(power_of_2, histogram.ranges(i)); + power_of_2 *= 2; + } + EXPECT_EQ(INT_MAX, histogram.ranges(8)); + + // Add i+1 samples to the i'th bucket. + histogram.Add(0); + 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; + } + // Leave overflow bucket empty. + + // Check to see that the bucket counts reflect our additions. + Histogram::SampleSet sample; + histogram.SnapshotSample(&sample); + EXPECT_EQ(INT_MAX, histogram.ranges(8)); + for (int i = 0; i < 8; i++) + EXPECT_EQ(i + 1, sample.counts(i)); +} + +static const wchar_t* kAssetTestHistogramName = L"AssetCountTest"; +static const wchar_t* kAssetTestDebugHistogramName = L"DAssetCountTest"; +void AssetCountFunction(int sample) { + ASSET_HISTOGRAM_COUNTS(kAssetTestHistogramName, sample); + DASSET_HISTOGRAM_COUNTS(kAssetTestDebugHistogramName, sample); +} +// Check that asset can be added and removed from buckets. +TEST(HistogramTest, AssetCountTest) { + // Start up a recorder system to identify all histograms. + StatisticsRecorder recorder; + + // Call through the macro to instantiate the static variables. + AssetCountFunction(100); // Put a sample in the bucket for 100. + + // Find the histogram. + StatisticsRecorder::Histograms histogram_list; + StatisticsRecorder::GetHistograms(&histogram_list); + ASSERT_GT(histogram_list.size(), 0u); + std::string ascii_name = WideToASCII(kAssetTestHistogramName); + std::string debug_ascii_name = WideToASCII(kAssetTestDebugHistogramName); + const Histogram* our_histogram = NULL; + const Histogram* our_debug_histogram = NULL; + for (StatisticsRecorder::Histograms::iterator it = histogram_list.begin(); + it != histogram_list.end(); + ++it) { + if (!(*it)->histogram_name().compare(ascii_name)) + our_histogram = *it; + else if (!(*it)->histogram_name().compare(debug_ascii_name)) { + our_debug_histogram = *it; + } + } + ASSERT_TRUE(our_histogram); +#ifndef NDEBUG + EXPECT_TRUE(our_debug_histogram); +#else + EXPECT_FALSE(our_debug_histogram); +#endif + // Verify it has a 1 in exactly one bucket (where we put the sample). + Histogram::SampleSet sample; + our_histogram->SnapshotSample(&sample); + int match_count = 0; + for (size_t i = 0; i < our_histogram->bucket_count(); ++i) { + if (sample.counts(i) > 0) { + EXPECT_LT(++match_count, 2) << "extra count in bucket " << i; + } + } + EXPECT_EQ(1u, match_count); + + // Remove our sample. + AssetCountFunction(-100); // Remove a sample from the bucket for 100. + our_histogram->SnapshotSample(&sample); // Extract data set. + + // Verify that the bucket is now empty, as are all the other buckets. + for (size_t i = 0; i < our_histogram->bucket_count(); ++i) { + EXPECT_EQ(0, sample.counts(i)) << "extra count in bucket " << i; + } + + if (!our_debug_histogram) + return; // This is a production build. + + // Repeat test with debug histogram. Note that insertion and deletion above + // should have cancelled each other out. + AssetCountFunction(100); // Add a sample into the bucket for 100. + our_debug_histogram->SnapshotSample(&sample); + match_count = 0; + for (size_t i = 0; i < our_debug_histogram->bucket_count(); ++i) { + if (sample.counts(i) > 0) { + EXPECT_LT(++match_count, 2) << "extra count in bucket " << i; + } + } + EXPECT_EQ(1u, match_count); + + // Remove our sample. + AssetCountFunction(-100); // Remove a sample from the bucket for 100. + our_debug_histogram->SnapshotSample(&sample); // Extract data set. + + // Verify that the bucket is now empty, as are all the other buckets. + for (size_t i = 0; i < our_debug_histogram->bucket_count(); ++i) { + EXPECT_EQ(0, sample.counts(i)) << "extra count in bucket " << i; + } +} + +} // namespace diff --git a/base/hmac.cc b/base/hmac.cc new file mode 100644 index 0000000..ee86ebd --- /dev/null +++ b/base/hmac.cc @@ -0,0 +1,121 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/hmac.h" +#include "base/logging.h" + +HMAC::HMAC(HashAlgorithm hash_alg, const unsigned char* key, int key_length) + : hash_alg_(hash_alg), + provider_(NULL), + hash_(NULL), + hkey_(NULL) { + if (!CryptAcquireContext(&provider_, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + provider_ = NULL; + ImportKey(key, key_length); +} + +HMAC::~HMAC() { + if (hkey_) + CryptDestroyKey(hkey_); + if (hash_) + CryptDestroyHash(hash_); + if (provider_) + CryptReleaseContext(provider_, 0); +} + +bool HMAC::Sign(const std::string& data, + unsigned char* digest, + int digest_length) { + if (!provider_ || !hkey_) + return false; + + switch (hash_alg_) { + case SHA1: + return SignWithSHA1(data, digest, digest_length); + default: + NOTREACHED(); + return false; + } +} + +void HMAC::ImportKey(const unsigned char* key, int key_length) { + if (key_length > kMaxKeySize) { + NOTREACHED(); + return; + } + + struct { + BLOBHEADER header; + DWORD key_size; + BYTE key_data[kMaxKeySize]; + } key_blob; + key_blob.header.bType = PLAINTEXTKEYBLOB; + key_blob.header.bVersion = CUR_BLOB_VERSION; + key_blob.header.reserved = 0; + key_blob.header.aiKeyAlg = CALG_RC2; + key_blob.key_size = key_length; + memcpy(key_blob.key_data, key, key_length); + + if (!CryptImportKey(provider_, + reinterpret_cast<const BYTE *>(&key_blob), + sizeof(key_blob), 0, 0, &hkey_)) + hkey_ = NULL; + + // Destroy the copy of the key. + SecureZeroMemory(key_blob.key_data, key_length); +} + +bool HMAC::SignWithSHA1(const std::string& data, + unsigned char* digest, + int digest_length) { + DCHECK(provider_); + DCHECK(hkey_); + + if (!CryptCreateHash(provider_, CALG_HMAC, hkey_, 0, &hash_)) + return false; + + HMAC_INFO hmac_info; + memset(&hmac_info, 0, sizeof(hmac_info)); + hmac_info.HashAlgid = CALG_SHA1; + if (!CryptSetHashParam(hash_, HP_HMAC_INFO, + reinterpret_cast<BYTE*>(&hmac_info), 0)) + return false; + + if (!CryptHashData(hash_, + reinterpret_cast<const BYTE*>(data.data()), + static_cast<DWORD>(data.size()), 0)) + return false; + + DWORD sha1_size = digest_length; + if (!CryptGetHashParam(hash_, HP_HASHVAL, digest, &sha1_size, 0)) + return false; + + return true; +} diff --git a/base/hmac.h b/base/hmac.h new file mode 100644 index 0000000..37c8fc9 --- /dev/null +++ b/base/hmac.h @@ -0,0 +1,87 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Utility class for calculating the HMAC for a given message. We currently +// only support SHA1 for the hash algorithm, but this can be extended easily. + +#ifndef BASE_HMAC_H__ +#define BASE_HMAC_H__ + +#include <windows.h> +#include <wincrypt.h> + +#include <string> + +#include "base/basictypes.h" + +class HMAC { + public: + // The set of supported hash functions. Extend as required. + enum HashAlgorithm { + SHA1 + }; + + HMAC(HashAlgorithm hash_alg, const unsigned char* key, int key_length); + ~HMAC(); + + // Returns the HMAC in 'digest' for the message in 'data' and the key + // specified in the contructor. + bool Sign(const std::string& data, unsigned char* digest, int digest_length); + + private: + // Import the key so that we don't have to store it ourself. + // TODO(paulg): Bug: http://b/1084719, 'ImportKey' will not currently work on + // Windows 2000 since it requires special handling for importing + // keys. See this link for details: + // http://www.derkeiler.com/Newsgroups/microsoft.public.platformsdk.security/2004-06/0270.html + void ImportKey(const unsigned char* key, int key_length); + + // Returns the SHA1 hash of 'data' and 'key' in 'digest'. If there was any + // error in the calculation, this method returns false, otherwise true. + bool SignWithSHA1(const std::string& data, + unsigned char* digest, + int digest_length); + + // Required for the SHA1 key_blob struct. We limit this to 16 bytes since + // Windows 2000 doesn't support keys larger than that. + static const int kMaxKeySize = 16; + + // The hash algorithm to use. + HashAlgorithm hash_alg_; + + // Windows Crypt API resources. + HCRYPTPROV provider_; + HCRYPTHASH hash_; + HCRYPTKEY hkey_; + + DISALLOW_EVIL_CONSTRUCTORS(HMAC); +}; + + +#endif // BASE_HMAC_H__
\ No newline at end of file diff --git a/base/hmac_unittest.cc b/base/hmac_unittest.cc new file mode 100644 index 0000000..1df5137 --- /dev/null +++ b/base/hmac_unittest.cc @@ -0,0 +1,94 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Program to generate an HMAC-SHA1 digest for a given string, for use in +// calculating and verifying SafeBrowsing MACs. + +#include <string> + +#include "base/hmac.h" +#include "base/no_windows2000_unittest.h" + +static const int kKeySize = 16; +static const int kDigestSize = 20; + +// Client key. +const unsigned char kClientKey[kKeySize] = + { 0xbf, 0xf6, 0x83, 0x4b, 0x3e, 0xa3, 0x23, 0xdd, + 0x96, 0x78, 0x70, 0x8e, 0xa1, 0x9d, 0x3b, 0x40 }; + +// Expected HMAC result using kMessage and kClientKey. +const unsigned char kReceivedHmac[kDigestSize] = + { 0xb9, 0x3c, 0xd6, 0xf0, 0x49, 0x47, 0xe2, 0x52, + 0x59, 0x7a, 0xbd, 0x1f, 0x2b, 0x4c, 0x83, 0xad, + 0x86, 0xd2, 0x48, 0x85 }; + +const char kMessage[] = +"n:1896\ni:goog-malware-shavar\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shav" +"ar_s_445-450\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_439-444\nu:s" +".ytimg.com/safebrowsing/rd/goog-malware-shavar_s_437\nu:s.ytimg.com/safebrowsi" +"ng/rd/goog-malware-shavar_s_436\nu:s.ytimg.com/safebrowsing/rd/goog-malware-sh" +"avar_s_433-435\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_431\nu:s.y" +"timg.com/safebrowsing/rd/goog-malware-shavar_s_430\nu:s.ytimg.com/safebrowsing" +"/rd/goog-malware-shavar_s_429\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shav" +"ar_s_428\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_426\nu:s.ytimg.c" +"om/safebrowsing/rd/goog-malware-shavar_s_424\nu:s.ytimg.com/safebrowsing/rd/go" +"og-malware-shavar_s_423\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_4" +"22\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_420\nu:s.ytimg.com/saf" +"ebrowsing/rd/goog-malware-shavar_s_419\nu:s.ytimg.com/safebrowsing/rd/goog-mal" +"ware-shavar_s_414\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_409-411" +"\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_405\nu:s.ytimg.com/safeb" +"rowsing/rd/goog-malware-shavar_s_404\nu:s.ytimg.com/safebrowsing/rd/goog-malwa" +"re-shavar_s_402\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_401\nu:s." +"ytimg.com/safebrowsing/rd/goog-malware-shavar_a_973-978\nu:s.ytimg.com/safebro" +"wsing/rd/goog-malware-shavar_a_937-972\nu:s.ytimg.com/safebrowsing/rd/goog-mal" +"ware-shavar_a_931-936\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_a_925" +"-930\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_a_919-924\ni:goog-phis" +"h-shavar\nu:s.ytimg.com/safebrowsing/rd/goog-phish-shavar_a_2633\nu:s.ytimg.co" +"m/safebrowsing/rd/goog-phish-shavar_a_2632\nu:s.ytimg.com/safebrowsing/rd/goog" +"-phish-shavar_a_2629-2631\nu:s.ytimg.com/safebrowsing/rd/goog-phish-shavar_a_2" +"626-2628\nu:s.ytimg.com/safebrowsing/rd/goog-phish-shavar_a_2625\n"; + +// TODO(paulg): Bug: http://b/1084719, skip this test on Windows 2000 until +// this bug is fixed. +class HMACTest : public NoWindows2000Test<testing::Test> { +}; + +TEST_F(HMACTest, HmacSafeBrowsingResponseTest) { + if (IsTestCaseDisabled()) + return; + + std::string message_data(kMessage); + + HMAC hmac(HMAC::SHA1, kClientKey, kKeySize); + unsigned char calculated_hmac[kDigestSize]; + + EXPECT_TRUE(hmac.Sign(message_data, calculated_hmac, kDigestSize)); + EXPECT_EQ(memcmp(kReceivedHmac, calculated_hmac, kDigestSize), 0); +} diff --git a/base/iat_patch.cc b/base/iat_patch.cc new file mode 100644 index 0000000..6c4fe1e --- /dev/null +++ b/base/iat_patch.cc @@ -0,0 +1,246 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/iat_patch.h" +#include "base/logging.h" + +namespace iat_patch { + +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; +}; + +static 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; +} + +static bool InterceptEnumCallback(const PEImage &image, const char* module, + DWORD ordinal, const char* name, DWORD hint, + IMAGE_THUNK_DATA* iat, void* cookie) { + InterceptFunctionInformation* intercept_information = + reinterpret_cast<InterceptFunctionInformation*>(cookie); + + if (NULL == intercept_information) { + NOTREACHED(); + return false; + } + + DCHECK(module); + + if ((0 == lstrcmpiA(module, intercept_information->imported_from_module)) && + (NULL != name) && + (0 == lstrcmpiA(name, intercept_information->function_name))) { + // Save the old pointer. + if (NULL != intercept_information->old_function) { + *(intercept_information->old_function) = GetIATFunction(iat); + } + + if (NULL != intercept_information->iat_thunk) { + *(intercept_information->iat_thunk) = iat; + } + + // portability check + COMPILE_ASSERT(sizeof(iat->u1.Function) == + sizeof(intercept_information->new_function), unknown_IAT_thunk_format); + + // Patch the function. + intercept_information->return_code = + ModifyCode(&(iat->u1.Function), + &(intercept_information->new_function), + sizeof(intercept_information->new_function)); + + // Terminate further enumeration. + intercept_information->finished_operation = true; + return false; + } + + return true; +} + +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; + } + + 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; +} + +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)); +} + +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; +} + +IATPatchFunction::IATPatchFunction() + : original_function_(NULL), + iat_thunk_(NULL), + intercept_function_(NULL) { +} + +IATPatchFunction::~IATPatchFunction() { + if (NULL != intercept_function_) { + DWORD error = Unpatch(); + DCHECK_EQ(NO_ERROR, error); + } +} + +DWORD IATPatchFunction::Patch(HMODULE module_handle, + const char* imported_from_module, + const char* function_name, + void* new_function) { + DCHECK_EQ(static_cast<void*>(NULL), original_function_); + DCHECK_EQ(static_cast<IMAGE_THUNK_DATA*>(NULL), iat_thunk_); + DCHECK_EQ(static_cast<void*>(NULL), intercept_function_); + + 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_); + intercept_function_ = new_function; + } + + return error; +} + +DWORD IATPatchFunction::Unpatch() { + DWORD error = RestoreImportedFunction(intercept_function_, + original_function_, + iat_thunk_); + + if (NO_ERROR == error) { + intercept_function_ = NULL; + original_function_ = NULL; + iat_thunk_ = NULL; + } + + return error; +} + +} // namespace iat_patch diff --git a/base/iat_patch.h b/base/iat_patch.h new file mode 100644 index 0000000..91e6f99 --- /dev/null +++ b/base/iat_patch.h @@ -0,0 +1,140 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file declares a helpers to intercept functions from a DLL. +// +// This set of functions are designed to 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. + +#ifndef BASE_IAT_PATCH_H__ +#define BASE_IAT_PATCH_H__ + +#include <windows.h> +#include "base/basictypes.h" +#include "base/pe_image.h" + +namespace iat_patch { + +// 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); + +// 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); + +// 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); + +// A class that encapsulates IAT patching helpers and restores +// the original function in the destructor. +class 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_handle 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 + // + DWORD Patch(HMODULE module_handle, + 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: + void* intercept_function_; + void* original_function_; + IMAGE_THUNK_DATA* iat_thunk_; + + DISALLOW_EVIL_CONSTRUCTORS(IATPatchFunction); +}; + +} // namespace iat_patch + +#endif // BASE_IAT_PATCH_H__ diff --git a/base/icu_util.cc b/base/icu_util.cc new file mode 100644 index 0000000..4e72632 --- /dev/null +++ b/base/icu_util.cc @@ -0,0 +1,70 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <string> + +#include "base/icu_util.h" + +#include "base/logging.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "unicode/udata.h" + +namespace icu_util { + +bool Initialize() { + // 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. +#ifndef DEBUG + static bool called_once = false; + DCHECK(!called_once); + called_once = true; +#endif + + // We expect to find the ICU data module alongside the current module. + std::wstring data_path; + PathService::Get(base::DIR_MODULE, &data_path); + file_util::AppendToPath(&data_path, L"icudt38.dll"); + + HMODULE module = LoadLibrary(data_path.c_str()); + if (!module) + return false; + + FARPROC addr = GetProcAddress(module, "icudt38_dat"); + if (!addr) + return false; + + UErrorCode err = U_ZERO_ERROR; + udata_setCommonData(reinterpret_cast<void*>(addr), &err); + return err == U_ZERO_ERROR; +} + +} // namespace icu_util diff --git a/base/icu_util.h b/base/icu_util.h new file mode 100644 index 0000000..d4af52c --- /dev/null +++ b/base/icu_util.h @@ -0,0 +1,41 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_ICU_UTIL_H__ +#define BASE_ICU_UTIL_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. +bool Initialize(); + +} // namespace icu_util + +#endif // BASE_ICU_UTIL_H__ diff --git a/base/id_map.h b/base/id_map.h new file mode 100644 index 0000000..e945f6a --- /dev/null +++ b/base/id_map.h @@ -0,0 +1,122 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_ID_MAP_H__ +#define BASE_ID_MAP_H__ + +// hash map is common (GCC4 and Dinkumware also support it, for example), but +// is not strictly part of the C++ standard. MS puts it into a funny namespace, +// although most other vendors seem to use std. This may have to change if +// other platforms are supported. +#include <hash_map> +using stdext::hash_map; + +#include "base/basictypes.h" +#include "base/logging.h" + +// 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. +template<class T> +class IDMap { + private: + typedef hash_map<int32, T*> HashTable; + + public: + IDMap() : next_id_(1) { + } + IDMap(const IDMap& other) : next_id_(other.next_id_), + data_(other.data_) { + } + + // support const iterators over the items + // Note, use iterator->first to get the ID, iterator->second to get the T* + typedef typename HashTable::const_iterator const_iterator; + const_iterator begin() const { + return data_.begin(); + } + const_iterator end() const { + return data_.end(); + } + + // Adds a view with an automatically generated unique ID. See AddWithID. + int32 Add(T* data) { + int32 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, int32 id) { + DCHECK(data_.find(id) == data_.end()) << "Inserting duplicate item"; + data_[id] = data; + } + + void Remove(int32 id) { + HashTable::iterator i = data_.find(id); + if (i == data_.end()) { + NOTREACHED() << "Attempting to remove an item not in the list"; + return; + } + data_.erase(i); + } + + bool IsEmpty() const { + return data_.empty(); + } + + T* Lookup(int32 id) const { + HashTable::const_iterator i = data_.find(id); + if (i == data_.end()) + return NULL; + return i->second; + } + + size_t size() const { + return data_.size(); + } + + protected: + // The next ID that we will return from Add() + int32 next_id_; + + HashTable data_; +}; + +#endif // BASE_ID_MAP_H__ diff --git a/base/idle_timer.cc b/base/idle_timer.cc new file mode 100644 index 0000000..a69cba3 --- /dev/null +++ b/base/idle_timer.cc @@ -0,0 +1,103 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/idle_timer.h" + +#include "base/message_loop.h" +#include "base/time.h" + +IdleTimerTask::IdleTimerTask(TimeDelta idle_time, bool repeat) + : idle_interval_(idle_time), + repeat_(repeat), + get_last_input_info_fn_(GetLastInputInfo) { +} + +IdleTimerTask::~IdleTimerTask() { + Stop(); +} + +void IdleTimerTask::Start() { + DCHECK(!timer_.get()); + StartTimer(); +} + +void IdleTimerTask::Stop() { + timer_.reset(); +} + +void IdleTimerTask::Run() { + // Verify we can fire the idle timer. + if (TimeUntilIdle().InMilliseconds() <= 0) { + OnIdle(); + last_time_fired_ = Time::Now(); + } + Stop(); + StartTimer(); // Restart the timer for next run. +} + +void IdleTimerTask::StartTimer() { + DCHECK(timer_ == NULL); + TimeDelta delay = TimeUntilIdle(); + if (delay.InMilliseconds() < 0) + delay = TimeDelta(); + timer_.reset(new OneShotTimer(delay)); + timer_->set_unowned_task(this); + timer_->Start(); +} + +TimeDelta IdleTimerTask::CurrentIdleTime() { + // TODO(mbelshe): This is windows-specific code. + LASTINPUTINFO info; + info.cbSize = sizeof(info); + if (get_last_input_info_fn_(&info)) { + // Note: GetLastInputInfo returns a 32bit value which rolls over ~49days. + int32 last_input_time = info.dwTime; + int32 current_time = GetTickCount(); + int32 interval = current_time - last_input_time; + // Interval will go negative if we've been idle for 2GB of ticks. + if (interval < 0) + interval = -interval; + return TimeDelta::FromMilliseconds(interval); + } + NOTREACHED(); + return TimeDelta::FromMilliseconds(0); +} + +TimeDelta IdleTimerTask::TimeUntilIdle() { + TimeDelta time_since_last_fire = Time::Now() - last_time_fired_; + TimeDelta current_idle_time = CurrentIdleTime(); + if (current_idle_time > time_since_last_fire) { + if (repeat_) + return idle_interval_ - time_since_last_fire; + return idle_interval_; + } + return idle_interval_ - current_idle_time; +} diff --git a/base/idle_timer.h b/base/idle_timer.h new file mode 100644 index 0000000..01f2fc0 --- /dev/null +++ b/base/idle_timer.h @@ -0,0 +1,114 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_IDLE_TIMER_H__ +#define BASE_IDLE_TIMER_H__ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/task.h" +#include "base/timer.h" + +// IdleTimer is a recurring Timer task which runs only when the system is idle. +// System Idle time is defined as not having any user keyboard or mouse +// activity for some period of time. Because the timer is user dependant, it +// is possible for the timer to never fire. + +// +// Usage should be for low-priority tasks, and may look like this: +// +// class MyIdleTimerTask : public IdleTimerTask { +// public: +// // This task will run after 5 seconds of idle time +// // and not more often than once per minute. +// MyIdleTimerTask() : IdleTimerTask(5, 60) {}; +// virtual void OnIdle() { do something }; +// } +// +// MyIdleTimerTask *task = new MyIdleTimerTask(); +// task->Start(); +// +// // As with all TimerTasks, the caller must dispose the object. +// delete task; // Will Stop the timer and cleanup the task. + +class TimerManager; + +// Function prototype for GetLastInputInfo. +typedef BOOL (__stdcall *GetLastInputInfoFunction)(PLASTINPUTINFO plii); + +class IdleTimerTask : public Task { + public: + // Create an IdleTimerTask. + // idle_time: idle time required before this task can run. + // repeat: true if the timer should fire multiple times per idle, + // false to fire once per idle. + IdleTimerTask(TimeDelta idle_time, bool repeat); + + // On destruction, the IdleTimerTask will Stop itself and delete the Task. + virtual ~IdleTimerTask(); + + // Start the IdleTimerTask. + void Start(); + + // Stop the IdleTimertask. + void Stop(); + + // The method to run when the timer elapses. + virtual void OnIdle() = 0; + + protected: + // Override the GetLastInputInfo function. + void set_last_input_info_fn(GetLastInputInfoFunction function) { + get_last_input_info_fn_ = function; + } + + private: + // This task's run method. + virtual void Run(); + + // Start the timer. + void StartTimer(); + + // Gets the number of milliseconds since the last input event. + TimeDelta CurrentIdleTime(); + + // Compute time until idle. Returns 0 if we are now idle. + TimeDelta TimeUntilIdle(); + + TimeDelta idle_interval_; + bool repeat_; + Time last_time_fired_; // The last time the idle timer fired. + // will be 0 until the timer fires the first time. + scoped_ptr<OneShotTimer> timer_; + + GetLastInputInfoFunction get_last_input_info_fn_; +}; + +#endif // BASE_IDLE_TIMER_H__ diff --git a/base/idletimer_unittest.cc b/base/idletimer_unittest.cc new file mode 100644 index 0000000..c3e8e6d --- /dev/null +++ b/base/idletimer_unittest.cc @@ -0,0 +1,221 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/idle_timer.h" +#include "base/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class IdleTimerTest : public testing::Test { + }; +}; + +// We Mock the GetLastInputInfo function to return +// the time stored here. +static DWORD mock_idle_time = GetTickCount(); + +BOOL __stdcall MockGetLastInputInfoFunction(PLASTINPUTINFO plii) { + DCHECK(plii->cbSize == sizeof(LASTINPUTINFO)); + plii->dwTime = mock_idle_time; + return TRUE; +} + +// TestIdle task fires after 100ms of idle time. +class TestIdleTask : public IdleTimerTask { + public: + TestIdleTask(bool repeat) + : IdleTimerTask(TimeDelta::FromMilliseconds(100), repeat), + idle_counter_(0) { + set_last_input_info_fn(MockGetLastInputInfoFunction); + } + + int get_idle_counter() { return idle_counter_; } + + virtual void OnIdle() { + idle_counter_++; + } + + private: + int idle_counter_; +}; + +// A task to help us quit the test. +class TestFinishedTask : public Task { + public: + TestFinishedTask() {} + void Run() { + MessageLoop::current()->Quit(); + } +}; + +// A timer which resets the idle clock. +class ResetIdleTask : public Task { + public: + ResetIdleTask() {} + void Run() { + mock_idle_time = GetTickCount(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// NoRepeat tests: +// A non-repeating idle timer will fire once on idle, and +// then will not fire again unless it goes non-idle first. + +TEST(IdleTimerTest, NoRepeatIdle) { + // Create an IdleTimer, which should fire once after 100ms. + // Create a Quit timer which will fire after 1s. + // Verify that we fired exactly once. + + mock_idle_time = GetTickCount(); + TestIdleTask test_task(false); + TestFinishedTask finish_task; + MessageLoop* loop = MessageLoop::current(); + Timer* t = loop->timer_manager()->StartTimer(1000, &finish_task, false); + test_task.Start(); + loop->Run(); + + EXPECT_EQ(test_task.get_idle_counter(), 1); + delete t; +} + +TEST(IdleTimerTest, NoRepeatFlipIdleOnce) { + // Create an IdleTimer, which should fire once after 100ms. + // Create a Quit timer which will fire after 1s. + // Create a timer to reset once, idle after 500ms. + // Verify that we fired exactly twice. + + mock_idle_time = GetTickCount(); + TestIdleTask test_task(false); + TestFinishedTask finish_task; + ResetIdleTask reset_task; + MessageLoop* loop = MessageLoop::current(); + Timer* t1 = loop->timer_manager()->StartTimer(1000, &finish_task, false); + Timer* t2 = loop->timer_manager()->StartTimer(500, &reset_task, false); + test_task.Start(); + loop->Run(); + + EXPECT_EQ(test_task.get_idle_counter(), 2); + delete t1; + delete t2; +} + +TEST(IdleTimerTest, NoRepeatNotIdle) { + // Create an IdleTimer, which should fire once after 100ms. + // Create a Quit timer which will fire after 1s. + // Create a timer to reset idle every 50ms. + // Verify that we never fired. + + mock_idle_time = GetTickCount(); + TestIdleTask test_task(false); + TestFinishedTask finish_task; + ResetIdleTask reset_task; + MessageLoop* loop = MessageLoop::current(); + Timer* t = loop->timer_manager()->StartTimer(1000, &finish_task, false); + Timer* reset_timer = loop->timer_manager()->StartTimer(50, &reset_task, true); + test_task.Start(); + loop->Run(); + loop->timer_manager()->StopTimer(reset_timer); + + EXPECT_EQ(test_task.get_idle_counter(), 0); + delete t; + delete reset_timer; +} + +/////////////////////////////////////////////////////////////////////////////// +// Repeat tests: +// A repeating idle timer will fire repeatedly on each interval, as long +// as it has been idle. So, if the machine remains idle, it will continue +// firing over and over. + +TEST(IdleTimerTest, Repeat) { + // Create an IdleTimer, which should fire repeatedly after 100ms. + // Create a Quit timer which will fire after 1.05s. + // Verify that we fired 10 times. + mock_idle_time = GetTickCount(); + TestIdleTask test_task(true); + TestFinishedTask finish_task; + MessageLoop* loop = MessageLoop::current(); + Timer* t = loop->timer_manager()->StartTimer(1050, &finish_task, false); + test_task.Start(); + loop->Run(); + + // In a perfect world, the idle_counter should be 10. However, + // since timers aren't guaranteed to fire perfectly, this can + // be less. Just expect more than 5 and no more than 10. + EXPECT_GT(test_task.get_idle_counter(), 5); + EXPECT_LE(test_task.get_idle_counter(), 10); + delete t; +} + +TEST(IdleTimerTest, RepeatIdleReset) { + // Create an IdleTimer, which should fire repeatedly after 100ms. + // Create a Quit timer which will fire after 1s. + // Create a reset timer, which fires after 550ms + // Verify that we fired 9 times. + mock_idle_time = GetTickCount(); + TestIdleTask test_task(true); + ResetIdleTask reset_task; + TestFinishedTask finish_task; + MessageLoop* loop = MessageLoop::current(); + Timer* t1 = loop->timer_manager()->StartTimer(1000, &finish_task, false); + Timer* t2 = loop->timer_manager()->StartTimer(550, &reset_task, false); + test_task.Start(); + loop->Run(); + + // In a perfect world, the idle_counter should be 9. However, + // since timers aren't guaranteed to fire perfectly, this can + // be less. Just expect more than 5 and no more than 9. + EXPECT_GT(test_task.get_idle_counter(), 5); + EXPECT_LE(test_task.get_idle_counter(), 9); + delete t1; + delete t2; +} + +TEST(IdleTimerTest, RepeatNotIdle) { + // Create an IdleTimer, which should fire repeatedly after 100ms. + // Create a Quit timer which will fire after 1s. + // Create a timer to reset idle every 50ms. + // Verify that we never fired. + + mock_idle_time = GetTickCount(); + TestIdleTask test_task(true); + TestFinishedTask finish_task; + ResetIdleTask reset_task; + MessageLoop* loop = MessageLoop::current(); + Timer* t1 = loop->timer_manager()->StartTimer(1000, &finish_task, false); + Timer* reset_timer = loop->timer_manager()->StartTimer(50, &reset_task, true); + test_task.Start(); + loop->Run(); + loop->timer_manager()->StopTimer(reset_timer); + + EXPECT_EQ(test_task.get_idle_counter(), 0); + delete t1; + delete reset_timer; +} diff --git a/base/image_util.cc b/base/image_util.cc new file mode 100644 index 0000000..172a595 --- /dev/null +++ b/base/image_util.cc @@ -0,0 +1,97 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <windows.h> +#include <ImageHlp.h> +#include <psapi.h> + +#include "base/image_util.h" +#include "base/process_util.h" + +// imagehlp.dll appears to ship in all win versions after Win95. +// nsylvain verified it is present in win2k. +// Using #pragma comment for dependency, instead of LoadLibrary/GetProcAddress. +#pragma comment(lib, "imagehlp.lib") + +namespace image_util { + +// ImageMetrics +ImageMetrics::ImageMetrics(HANDLE process) : process_(process) { +} + +ImageMetrics::~ImageMetrics() { +} + +bool ImageMetrics::GetDllImageSectionData(const std::string& loaded_dll_name, + ImageSectionsData* section_sizes) { + // Get a handle to the loaded DLL + HMODULE the_dll = GetModuleHandleA(loaded_dll_name.c_str()); + char full_filename[MAX_PATH]; + // Get image path + if (GetModuleFileNameExA(process_, the_dll, full_filename, MAX_PATH)) { + return GetImageSectionSizes(full_filename, section_sizes); + } + return false; +} + +bool ImageMetrics::GetProcessImageSectionData(ImageSectionsData* + section_sizes) { + char exe_path[MAX_PATH]; + // Get image path + if (GetModuleFileNameExA(process_, NULL, exe_path, MAX_PATH)) { + return GetImageSectionSizes(exe_path, section_sizes); + } + return false; +} + +// private +bool ImageMetrics::GetImageSectionSizes(char* qualified_path, + ImageSectionsData* result) { + LOADED_IMAGE li; + // TODO (timsteele): There is no unicode version for MapAndLoad, hence + // why ansi functions are used in this class. Should we try and rewrite + // this call ourselves to be safe? + if (MapAndLoad(qualified_path, 0, &li, FALSE, TRUE)) { + IMAGE_SECTION_HEADER* section_header = li.Sections; + for (unsigned i = 0; i < li.NumberOfSections; i++, section_header++) { + std::string name(reinterpret_cast<char*>(section_header->Name)); + ImageSectionData data(name, section_header->Misc.VirtualSize ? + section_header->Misc.VirtualSize : + section_header->SizeOfRawData); + // copy into result + result->push_back(data); + } + } else { + // map and load failed + return false; + } + UnMapAndLoad(&li); + return true; +} + +} // namespace image_util diff --git a/base/image_util.h b/base/image_util.h new file mode 100644 index 0000000..094da08 --- /dev/null +++ b/base/image_util.h @@ -0,0 +1,88 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file/namespace contains utility functions for gathering +// information about PE (Portable Executable) headers within +// images (dll's / exe's ) + +#ifndef BASE_IMAGE_UTIL_H__ +#define BASE_IMAGE_UTIL_H__ + +#include <vector> +#include "base/basictypes.h" +#include <windows.h> + +namespace image_util { + +// Contains both the PE section name (.text, .reloc etc) and its size. +struct ImageSectionData { + std::string name; + size_t size_in_bytes; + ImageSectionData (const std::string& section_name, size_t section_size) : + name (section_name), size_in_bytes(section_size) { + } +}; + +typedef std::vector<ImageSectionData> ImageSectionsData; + +// Provides image statistics for modules of a specified process, or for the +// specified process' own executable file. To use, invoke CreateImageMetrics() +// to get an instance for a specified process, then access the information via +// methods. +class ImageMetrics { + public: + // Creates an ImageMetrics instance for given process owned by + // the caller. + explicit ImageMetrics(HANDLE process); + ~ImageMetrics(); + + // Fills a vector of ImageSectionsData containing name/size info + // for every section found in the specified dll's PE section table. + // The DLL must be loaded by the process associated with this ImageMetrics + // instance. + bool GetDllImageSectionData(const std::string& loaded_dll_name, + ImageSectionsData* section_sizes); + + // Fills a vector if ImageSectionsData containing name/size info + // for every section found in the executable file of the process + // associated with this ImageMetrics instance. + bool GetProcessImageSectionData(ImageSectionsData* section_sizes); + + private: + // Helper for GetDllImageSectionData and GetProcessImageSectionData + bool GetImageSectionSizes(char* qualified_path, ImageSectionsData* result); + + HANDLE process_; + + DISALLOW_EVIL_CONSTRUCTORS(ImageMetrics); +}; + +} // namespace image_util + +#endif
\ No newline at end of file diff --git a/base/json_reader.cc b/base/json_reader.cc new file mode 100644 index 0000000..1ec5f637 --- /dev/null +++ b/base/json_reader.cc @@ -0,0 +1,605 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/json_reader.h" + +#include <float.h> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/values.h" + +static const JSONReader::Token kInvalidToken(JSONReader::Token::INVALID_TOKEN, + 0, 0); +static const int kStackLimit = 100; + +namespace { + +inline int HexToInt(wchar_t c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } else if ('A' <= c && c <= 'F') { + return c - 'A' + 10; + } else if ('a' <= c && c <= 'f') { + return c - 'a' + 10; + } + NOTREACHED(); + return 0; +} + +// A helper method for ParseNumberToken. It reads an int from the end of +// token. The method returns false if there is no valid integer at the end of +// the token. +bool ReadInt(JSONReader::Token& token, bool can_have_leading_zeros) { + wchar_t first = token.NextChar(); + int len = 0; + + // Read in more digits + wchar_t c = first; + while ('\0' != c && '0' <= c && c <= '9') { + ++token.length; + ++len; + c = token.NextChar(); + } + // We need at least 1 digit. + if (len == 0) + return false; + + if (!can_have_leading_zeros && len > 1 && '0' == first) + return false; + + return true; +} + +// A helper method for ParseStringToken. It reads |digits| hex digits from the +// token. If the sequence if digits is not valid (contains other characters), +// the method returns false. +bool ReadHexDigits(JSONReader::Token& token, int digits) { + for (int i = 1; i <= digits; ++i) { + wchar_t c = *(token.begin + token.length + i); + if ('\0' == c) + return false; + if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || + ('A' <= c && c <= 'F'))) { + return false; + } + } + + token.length += digits; + return true; +} + +} // anonymous namespace + +/* static */ +bool JSONReader::Read(const std::string& json, Value** root) { + return JsonToValue(json, root, true); +} + +/* static */ +bool JSONReader::JsonToValue(const std::string& json, Value** root, + bool check_root) { + // Assume input is UTF8. The conversion from UTF8 to wstring removes null + // bytes for us (a good thing). + std::wstring json_wide(UTF8ToWide(json)); + const wchar_t* json_cstr = json_wide.c_str(); + + // When the input JSON string starts with a UTF-8 Byte-Order-Mark + // (0xEF, 0xBB, 0xBF), the UTF8ToWide() function converts it to a Unicode + // BOM (U+FEFF). To avoid the JSONReader::BuildValue() function from + // mis-treating a Unicode BOM as an invalid character and returning false, + // skip a converted Unicode BOM if it exists. + if (!json_wide.empty() && json_cstr[0] == 0xFEFF) { + ++json_cstr; + } + + JSONReader reader(json_cstr); + + Value* temp_root = NULL; + bool success = reader.BuildValue(&temp_root, check_root); + + // Only modify root_ if we have valid JSON and nothing else. + if (success && reader.ParseToken().type == Token::END_OF_INPUT) { + *root = temp_root; + return true; + } + + if (temp_root) + delete temp_root; + return false; +} + +JSONReader::JSONReader(const wchar_t* json_start_pos) + : json_pos_(json_start_pos), stack_depth_(0) {} + +bool JSONReader::BuildValue(Value** node, bool is_root) { + ++stack_depth_; + if (stack_depth_ > kStackLimit) + return false; + + Token token = ParseToken(); + // The root token must be an array or an object. + if (is_root && token.type != Token::OBJECT_BEGIN && + token.type != Token::ARRAY_BEGIN) { + return false; + } + + switch (token.type) { + case Token::END_OF_INPUT: + case Token::INVALID_TOKEN: + return false; + + case Token::NULL_TOKEN: + *node = Value::CreateNullValue(); + break; + + case Token::BOOL_TRUE: + *node = Value::CreateBooleanValue(true); + break; + + case Token::BOOL_FALSE: + *node = Value::CreateBooleanValue(false); + break; + + case Token::NUMBER: + if (!DecodeNumber(token, node)) + return false; + break; + + case Token::STRING: + if (!DecodeString(token, node)) + return false; + break; + + case Token::ARRAY_BEGIN: + { + json_pos_ += token.length; + token = ParseToken(); + + ListValue* array = new ListValue; + while (token.type != Token::ARRAY_END) { + Value* array_node = NULL; + if (!BuildValue(&array_node, false)) { + delete array; + return false; + } + array->Append(array_node); + + // After a list value, we expect a comma or the end of the list. + token = ParseToken(); + if (token.type == Token::LIST_SEPARATOR) { + json_pos_ += token.length; + token = ParseToken(); + // Trailing commas are invalid + if (token.type == Token::ARRAY_END) { + delete array; + return false; + } + } else if (token.type != Token::ARRAY_END) { + // Unexpected value after list value. Bail out. + delete array; + return false; + } + } + if (token.type != Token::ARRAY_END) { + delete array; + return false; + } + *node = array; + break; + } + + case Token::OBJECT_BEGIN: + { + json_pos_ += token.length; + token = ParseToken(); + + DictionaryValue* dict = new DictionaryValue; + while (token.type != Token::OBJECT_END) { + if (token.type != Token::STRING) { + delete dict; + return false; + } + Value* dict_key_value = NULL; + if (!DecodeString(token, &dict_key_value)) { + delete dict; + return false; + } + // Convert the key into a wstring. + std::wstring dict_key; + bool success = dict_key_value->GetAsString(&dict_key); + DCHECK(success); + delete dict_key_value; + + json_pos_ += token.length; + token = ParseToken(); + if (token.type != Token::OBJECT_PAIR_SEPARATOR) { + delete dict; + return false; + } + + json_pos_ += token.length; + token = ParseToken(); + Value* dict_value = NULL; + if (!BuildValue(&dict_value, false)) { + delete dict; + return false; + } + dict->Set(dict_key, dict_value); + + // After a key/value pair, we expect a comma or the end of the + // object. + token = ParseToken(); + if (token.type == Token::LIST_SEPARATOR) { + json_pos_ += token.length; + token = ParseToken(); + // Trailing commas are invalid. TODO(tc): Should we allow trailing + // commas in objects? Seems harmless and quite convenient... + if (token.type == Token::OBJECT_END) { + delete dict; + return false; + } + } else if (token.type != Token::OBJECT_END) { + // Unexpected value after last object value. Bail out. + delete dict; + return false; + } + } + if (token.type != Token::OBJECT_END) { + delete dict; + return false; + } + *node = dict; + break; + } + + default: + // We got a token that's not a value. + return false; + } + json_pos_ += token.length; + + --stack_depth_; + return true; +} + +JSONReader::Token JSONReader::ParseNumberToken() { + // We just grab the number here. We validate the size in DecodeNumber. + // According to RFC4627, a valid number is: [minus] int [frac] [exp] + Token token(Token::NUMBER, json_pos_, 0); + wchar_t c = *json_pos_; + if ('-' == c) { + ++token.length; + c = token.NextChar(); + } + + if (!ReadInt(token, false)) + return kInvalidToken; + + // Optional fraction part + c = token.NextChar(); + if ('.' == c) { + ++token.length; + if (!ReadInt(token, true)) + return kInvalidToken; + c = token.NextChar(); + } + + // Optional exponent part + if ('e' == c || 'E' == c) { + ++token.length; + c = token.NextChar(); + if ('-' == c || '+' == c) { + ++token.length; + c = token.NextChar(); + } + if (!ReadInt(token, true)) + return kInvalidToken; + } + + return token; +} + +bool JSONReader::DecodeNumber(const Token& token, Value** node) { + // Determine if we want to try to parse as an int or a double. + bool is_double = false; + for (int i = 0; i < token.length; ++i) { + wchar_t c = *(token.begin + i); + if ('e' == c || 'E' == c || '.' == c) { + is_double = true; + break; + } + } + + if (is_double) { + // Try parsing as a double. + double num_double; + int parsed_values = swscanf_s(token.begin, L"%lf", &num_double); + // Make sure we're not -INF, INF or NAN. + if (1 == parsed_values && _finite(num_double)) { + *node = Value::CreateRealValue(num_double); + return true; + } + } else { + int num_int; + int parsed_values = swscanf_s(token.begin, L"%d", &num_int); + if (1 == parsed_values) { + // Ensure the parsed value matches the string. This makes sure we don't + // overflow/underflow. + const std::wstring& back_to_str = StringPrintf(L"%d", num_int); + if (0 == wcsncmp(back_to_str.c_str(), token.begin, + back_to_str.length())) { + *node = Value::CreateIntegerValue(num_int); + return true; + } + } + } + return false; +} + +JSONReader::Token JSONReader::ParseStringToken() { + Token token(Token::STRING, json_pos_, 1); + wchar_t c = token.NextChar(); + while ('\0' != c) { + if ('\\' == c) { + ++token.length; + c = token.NextChar(); + // Make sure the escaped char is valid. + switch (c) { + case 'x': + if (!ReadHexDigits(token, 2)) + return kInvalidToken; + break; + case 'u': + if (!ReadHexDigits(token, 4)) + return kInvalidToken; + break; + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case '"': + break; + default: + return kInvalidToken; + } + } else if ('"' == c) { + ++token.length; + return token; + } + ++token.length; + c = token.NextChar(); + } + return kInvalidToken; +} + +bool JSONReader::DecodeString(const Token& token, Value** node) { + std::wstring decoded_str; + decoded_str.reserve(token.length - 2); + + for (int i = 1; i < token.length - 1; ++i) { + wchar_t c = *(token.begin + i); + if ('\\' == c) { + ++i; + c = *(token.begin + i); + switch (c) { + case '"': + case '/': + case '\\': + decoded_str.push_back(c); + break; + case 'b': + decoded_str.push_back('\b'); + break; + case 'f': + decoded_str.push_back('\f'); + break; + case 'n': + decoded_str.push_back('\n'); + break; + case 'r': + decoded_str.push_back('\r'); + break; + case 't': + decoded_str.push_back('\t'); + break; + + case 'x': + decoded_str.push_back((HexToInt(*(token.begin + i + 1)) << 4) + + HexToInt(*(token.begin + i + 2))); + i += 2; + break; + case 'u': + decoded_str.push_back((HexToInt(*(token.begin + i + 1)) << 12 ) + + (HexToInt(*(token.begin + i + 2)) << 8) + + (HexToInt(*(token.begin + i + 3)) << 4) + + HexToInt(*(token.begin + i + 4))); + i += 4; + break; + + default: + // We should only have valid strings at this point. If not, + // ParseStringToken didn't do it's job. + NOTREACHED(); + return false; + } + } else { + // Not escaped + decoded_str.push_back(c); + } + } + *node = Value::CreateStringValue(decoded_str); + + return true; +} + +JSONReader::Token JSONReader::ParseToken() { + static const std::wstring kNullString(L"null"); + static const std::wstring kTrueString(L"true"); + static const std::wstring kFalseString(L"false"); + + EatWhitespaceAndComments(); + + Token token(Token::INVALID_TOKEN, 0, 0); + switch (*json_pos_) { + case '\0': + token.type = Token::END_OF_INPUT; + break; + + case 'n': + if (NextStringMatch(kNullString)) + token = Token(Token::NULL_TOKEN, json_pos_, 4); + break; + + case 't': + if (NextStringMatch(kTrueString)) + token = Token(Token::BOOL_TRUE, json_pos_, 4); + break; + + case 'f': + if (NextStringMatch(kFalseString)) + token = Token(Token::BOOL_FALSE, json_pos_, 5); + break; + + case '[': + token = Token(Token::ARRAY_BEGIN, json_pos_, 1); + break; + + case ']': + token = Token(Token::ARRAY_END, json_pos_, 1); + break; + + case ',': + token = Token(Token::LIST_SEPARATOR, json_pos_, 1); + break; + + case '{': + token = Token(Token::OBJECT_BEGIN, json_pos_, 1); + break; + + case '}': + token = Token(Token::OBJECT_END, json_pos_, 1); + break; + + case ':': + token = Token(Token::OBJECT_PAIR_SEPARATOR, json_pos_, 1); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token = ParseNumberToken(); + break; + + case '"': + token = ParseStringToken(); + break; + } + return token; +} + +bool JSONReader::NextStringMatch(const std::wstring& str) { + for (size_t i = 0; i < str.length(); ++i) { + if ('\0' == *json_pos_) + return false; + if (*(json_pos_ + i) != str[i]) + return false; + } + return true; +} + +void JSONReader::EatWhitespaceAndComments() { + while ('\0' != *json_pos_) { + switch (*json_pos_) { + case ' ': + case '\n': + case '\r': + case '\t': + ++json_pos_; + break; + case '/': + // TODO(tc): This isn't in the RFC so it should be a parser flag. + if (!EatComment()) + return; + break; + default: + // Not a whitespace char, just exit. + return; + } + } +} + +bool JSONReader::EatComment() { + if ('/' != *json_pos_) + return false; + + wchar_t next_char = *(json_pos_ + 1); + if ('/' == next_char) { + // Line comment, read until \n or \r + json_pos_ += 2; + while ('\0' != *json_pos_) { + switch (*json_pos_) { + case '\n': + case '\r': + ++json_pos_; + return true; + default: + ++json_pos_; + } + } + } else if ('*' == next_char) { + // Block comment, read until */ + json_pos_ += 2; + while ('\0' != *json_pos_) { + switch (*json_pos_) { + case '*': + if ('/' == *(json_pos_ + 1)) { + json_pos_ += 2; + return true; + } + default: + ++json_pos_; + } + } + } else { + return false; + } + return true; +} diff --git a/base/json_reader.h b/base/json_reader.h new file mode 100644 index 0000000..97f68fd --- /dev/null +++ b/base/json_reader.h @@ -0,0 +1,165 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// 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): It would be nice to give back an error string when we fail to parse JSON. +// Parsing options: +// - Relax trailing commas in arrays and objects +// - Relax object keys being wrapped in double quotes +// - Disable comment stripping + +#ifndef CHROME_COMMON_JSON_READER_H__ +#define CHROME_COMMON_JSON_READER_H__ + +#include <string> + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +class Value; + +class JSONReader { + public: + // A struct to hold a JS token. + class Token { + public: + enum Type { + OBJECT_BEGIN, // { + OBJECT_END, // } + ARRAY_BEGIN, // [ + ARRAY_END, // ] + STRING, + NUMBER, + BOOL_TRUE, // true + BOOL_FALSE, // false + NULL_TOKEN, // null + LIST_SEPARATOR, // , + OBJECT_PAIR_SEPARATOR, // : + END_OF_INPUT, + INVALID_TOKEN, + }; + Token(Type t, const wchar_t* b, int len) + : type(t), begin(b), length(len) {} + + Type type; + + // A pointer into JSONReader::json_pos_ that's the beginning of this token. + const wchar_t* begin; + + // End should be one char past the end of the token. + int length; + + // Get the character that's one past the end of this token. + wchar_t NextChar() { + return *(begin + length); + } + }; + + // Reads and parses |json| and populates |root|. If |json| is not a + // properly formed JSON string, returns false and leaves root unaltered. + static bool Read(const std::string& json, Value** root); + + private: + JSONReader(const wchar_t* json_start_pos); + DISALLOW_EVIL_CONSTRUCTORS(JSONReader); + + FRIEND_TEST(JSONReaderTest, Reading); + + // Pass through method from JSONReader::Read. We have this so unittests can + // disable the root check. + static bool JsonToValue(const std::string& json, Value** root, + bool check_root); + + // Recursively build Value. Returns false if we don't have a valid JSON + // string. If |is_root| is true, we verify that the root element is either + // an object or an array. + bool BuildValue(Value** root, bool is_root); + + // Parses a sequence of characters into a Token::NUMBER. If the sequence of + // characters is not a valid number, returns a Token::INVALID_TOKEN. Note + // that DecodeNumber is used to actually convert from a string to an + // int/double. + Token ParseNumberToken(); + + // Try and convert the substring that token holds into an int or a double. If + // we can (ie., no overflow), return true and create the appropriate value + // for |node|. Return false if we can't do the conversion. + bool DecodeNumber(const Token& token, Value** node); + + // Parses a sequence of characters into a Token::STRING. If the sequence of + // characters is not a valid string, returns a Token::INVALID_TOKEN. Note + // that DecodeString is used to actually decode the escaped string into an + // actual wstring. + Token ParseStringToken(); + + // Convert the substring into a value string. This should always succeed + // (otherwise ParseStringToken would have failed), but returns a success bool + // just in case. + bool DecodeString(const Token& token, Value** node); + + // Grabs the next token in the JSON stream. This does not increment the + // stream so it can be used to look ahead at the next token. + Token ParseToken(); + + // Increments json_pos_ past leading whitespace and comments. + void EatWhitespaceAndComments(); + + // If json_pos_ is at the start of a comment, eat it, otherwise, returns + // false. + bool EatComment(); + + // Checks if json_pos_ matches str. + bool NextStringMatch(const std::wstring& str); + + // Pointer to the current position in the input string. + const wchar_t* json_pos_; + + // Used to keep track of how many nested lists/dicts there are. + int stack_depth_; +}; + +#endif // CHROME_COMMON_JSON_READER_H__ diff --git a/base/json_reader_unittest.cc b/base/json_reader_unittest.cc new file mode 100644 index 0000000..f218531 --- /dev/null +++ b/base/json_reader_unittest.cc @@ -0,0 +1,412 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/json_reader.h" +#include "base/values.h" + +TEST(JSONReaderTest, Reading) { + // some whitespace checking + Value* root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue(" null ", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_NULL)); + delete root; + + // Invalid JSON string + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("nu", &root, false)); + ASSERT_FALSE(root); + + // Simple bool + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("true ", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_BOOLEAN)); + delete root; + + // Test number formats + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("43", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_INTEGER)); + int int_val = 0; + ASSERT_TRUE(root->GetAsInteger(&int_val)); + ASSERT_EQ(43, int_val); + delete root; + + // According to RFC4627, oct, hex, and leading zeros are invalid JSON. + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("043", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("0x43", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("00", &root, false)); + ASSERT_FALSE(root); + + // Test 0 (which needs to be special cased because of the leading zero + // clause). + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("0", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_INTEGER)); + int_val = 1; + ASSERT_TRUE(root->GetAsInteger(&int_val)); + ASSERT_EQ(0, int_val); + delete root; + + // Numbers that overflow ints should fail + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("2147483648", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("-2147483649", &root, false)); + ASSERT_FALSE(root); + + // Parse a double + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("43.1", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_REAL)); + double real_val = 0.0; + ASSERT_TRUE(root->GetAsReal(&real_val)); + ASSERT_DOUBLE_EQ(43.1, real_val); + delete root; + + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("4.3e-1", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_REAL)); + real_val = 0.0; + ASSERT_TRUE(root->GetAsReal(&real_val)); + ASSERT_DOUBLE_EQ(.43, real_val); + delete root; + + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("2.1e0", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_REAL)); + real_val = 0.0; + ASSERT_TRUE(root->GetAsReal(&real_val)); + ASSERT_DOUBLE_EQ(2.1, real_val); + delete root; + + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("2.1e+0001", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_REAL)); + real_val = 0.0; + ASSERT_TRUE(root->GetAsReal(&real_val)); + ASSERT_DOUBLE_EQ(21.0, real_val); + delete root; + + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("0.01", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_REAL)); + real_val = 0.0; + ASSERT_TRUE(root->GetAsReal(&real_val)); + ASSERT_DOUBLE_EQ(0.01, real_val); + delete root; + + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("1.00", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_REAL)); + real_val = 0.0; + ASSERT_TRUE(root->GetAsReal(&real_val)); + ASSERT_DOUBLE_EQ(1.0, real_val); + delete root; + + // Fractional parts must have a digit before and after the decimal point. + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("1.", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue(".1", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("1.e10", &root, false)); + ASSERT_FALSE(root); + + // Exponent must have a digit following the 'e'. + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("1e", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("1E", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("1e1.", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("1e1.0", &root, false)); + ASSERT_FALSE(root); + + // INF/-INF/NaN are not valid + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("1e1000", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("-1e1000", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("NaN", &root, false)); + ASSERT_FALSE(root); + + // Invalid number formats + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("4.3.1", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("4e3.1", &root, false)); + ASSERT_FALSE(root); + + // Test string parser + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("\"hello world\"", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_STRING)); + std::wstring str_val; + ASSERT_TRUE(root->GetAsString(&str_val)); + ASSERT_EQ(L"hello world", str_val); + delete root; + + // Empty string + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("\"\"", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + ASSERT_TRUE(root->GetAsString(&str_val)); + ASSERT_EQ(L"", str_val); + delete root; + + // Test basic string escapes + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("\" \\\"\\\\\\/\\b\\f\\n\\r\\t\"", &root, + false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + ASSERT_TRUE(root->GetAsString(&str_val)); + ASSERT_EQ(L" \"\\/\b\f\n\r\t", str_val); + delete root; + + // Test hex and unicode escapes including the null character. + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("\"\\x41\\x00\\u1234\"", &root, false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + ASSERT_TRUE(root->GetAsString(&str_val)); + ASSERT_EQ(std::wstring(L"A\0\x1234", 3), str_val); + delete root; + + // Test invalid strings + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("\"no closing quote", &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("\"\\z invalid escape char\"", &root, + false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("\"\\xAQ invalid hex code\"", &root, + false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("not enough hex chars\\x1\"", &root, + false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("\"not enough escape chars\\u123\"", + &root, false)); + ASSERT_FALSE(root); + root = NULL; + ASSERT_FALSE(JSONReader::JsonToValue("\"extra backslash at end of input\\\"", + &root, false)); + ASSERT_FALSE(root); + + // Basic array + root = NULL; + ASSERT_TRUE(JSONReader::Read("[true, false, null]", &root)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_LIST)); + ListValue* list = static_cast<ListValue*>(root); + ASSERT_EQ(3, list->GetSize()); + delete root; + + // Empty array + root = NULL; + ASSERT_TRUE(JSONReader::Read("[]", &root)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root); + ASSERT_EQ(0, list->GetSize()); + delete root; + + // Nested arrays + root = NULL; + ASSERT_TRUE(JSONReader::Read("[[true], [], [false, [], [null]], null]", &root)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root); + ASSERT_EQ(4, list->GetSize()); + delete root; + + // Invalid, missing close brace. + root = NULL; + ASSERT_FALSE(JSONReader::Read("[[true], [], [false, [], [null]], null", &root)); + ASSERT_FALSE(root); + + // Invalid, too many commas + root = NULL; + ASSERT_FALSE(JSONReader::Read("[true,, null]", &root)); + ASSERT_FALSE(root); + + // Invalid, no commas + root = NULL; + ASSERT_FALSE(JSONReader::Read("[true null]", &root)); + ASSERT_FALSE(root); + + // Invalid, trailing comma + root = NULL; + ASSERT_FALSE(JSONReader::Read("[true,]", &root)); + ASSERT_FALSE(root); + + // Test objects + root = NULL; + ASSERT_TRUE(JSONReader::Read("{}", &root)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + delete root; + + root = NULL; + ASSERT_TRUE(JSONReader::Read( + "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\" }", &root)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + DictionaryValue* dict_val = static_cast<DictionaryValue*>(root); + real_val = 0.0; + ASSERT_TRUE(dict_val->GetReal(L"number", &real_val)); + ASSERT_DOUBLE_EQ(9.87654321, real_val); + Value* null_val = NULL; + ASSERT_TRUE(dict_val->Get(L"null", &null_val)); + ASSERT_TRUE(null_val->IsType(Value::TYPE_NULL)); + str_val.clear(); + ASSERT_TRUE(dict_val->GetString(L"S", &str_val)); + ASSERT_EQ(L"str", str_val); + delete root; + + // Test nesting + root = NULL; + ASSERT_TRUE(JSONReader::Read( + "{\"inner\":{\"array\":[true]},\"false\":false,\"d\":{}}", &root)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + dict_val = static_cast<DictionaryValue*>(root); + DictionaryValue* inner_dict = NULL; + ASSERT_TRUE(dict_val->GetDictionary(L"inner", &inner_dict)); + ListValue* inner_array = NULL; + ASSERT_TRUE(inner_dict->GetList(L"array", &inner_array)); + ASSERT_EQ(1, inner_array->GetSize()); + bool bool_value = true; + ASSERT_TRUE(dict_val->GetBoolean(L"false", &bool_value)); + ASSERT_FALSE(bool_value); + inner_dict = NULL; + ASSERT_TRUE(dict_val->GetDictionary(L"d", &inner_dict)); + delete root; + + // Invalid, no closing brace + root = NULL; + ASSERT_FALSE(JSONReader::Read("{\"a\": true", &root)); + ASSERT_FALSE(root); + + // Invalid, keys must be quoted + root = NULL; + ASSERT_FALSE(JSONReader::Read("{foo:true}", &root)); + ASSERT_FALSE(root); + + // Invalid, trailing comma + root = NULL; + ASSERT_FALSE(JSONReader::Read("{\"a\":true,}", &root)); + ASSERT_FALSE(root); + + // Invalid, too many commas + root = NULL; + ASSERT_FALSE(JSONReader::Read("{\"a\":true,,\"b\":false}", &root)); + ASSERT_FALSE(root); + + // Invalid, no separator + root = NULL; + ASSERT_FALSE(JSONReader::Read("{\"a\" \"b\"}", &root)); + ASSERT_FALSE(root); + + // Test stack overflow + root = NULL; + std::string evil(1000000, '['); + evil.append(std::string(1000000, ']')); + ASSERT_FALSE(JSONReader::Read(evil, &root)); + ASSERT_FALSE(root); + + // 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("[]]"); + ASSERT_TRUE(JSONReader::Read(not_evil, &root)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root); + ASSERT_EQ(5001, list->GetSize()); + delete root; + + // Test utf8 encoded input + root = NULL; + ASSERT_TRUE(JSONReader::JsonToValue("\"\xe7\xbd\x91\xe9\xa1\xb5\"", &root, + false)); + ASSERT_TRUE(root); + ASSERT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + ASSERT_TRUE(root->GetAsString(&str_val)); + ASSERT_EQ(L"\x7f51\x9875", str_val); + delete root; + + // Test invalid root objects. + root = NULL; + ASSERT_FALSE(JSONReader::Read("null", &root)); + ASSERT_FALSE(JSONReader::Read("true", &root)); + ASSERT_FALSE(JSONReader::Read("10", &root)); + ASSERT_FALSE(JSONReader::Read("\"root\"", &root)); +} diff --git a/base/json_writer.cc b/base/json_writer.cc new file mode 100644 index 0000000..512e0a1 --- /dev/null +++ b/base/json_writer.cc @@ -0,0 +1,191 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/json_writer.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/values.h" +#include "base/string_escape.h" + +const char kPrettyPrintLineEnding[] = "\r\n"; + +/* static */ +void JSONWriter::Write(const Value* const node, bool pretty_print, + std::string* json) { + json->clear(); + // Is there a better way to estimate the size of the output? + json->reserve(1024); + JSONWriter writer(pretty_print, json); + writer.BuildJSONString(node, 0); + if (pretty_print) + json->append(kPrettyPrintLineEnding); +} + +JSONWriter::JSONWriter(bool pretty_print, std::string* json) + : 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); + StringAppendF(json_string_, "%d", value); + break; + } + + case Value::TYPE_REAL: + { + double value; + bool result = node->GetAsReal(&value); + DCHECK(result); + std::string real = StringPrintf("%g", 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"); + } + json_string_->append(real); + break; + } + + case Value::TYPE_STRING: + { + std::wstring value; + bool result = node->GetAsString(&value); + DCHECK(result); + AppendQuotedString(value); + break; + } + + case Value::TYPE_LIST: + { + json_string_->append("["); + if (pretty_print_) + json_string_->append(" "); + + const ListValue* list = static_cast<const ListValue*>(node); + for (size_t i = 0; i < list->GetSize(); ++i) { + if (i != 0) { + json_string_->append(","); + if (pretty_print_) + json_string_->append(" "); + } + + Value* value = NULL; + bool result = list->Get(i, &value); + DCHECK(result); + BuildJSONString(value, depth); + } + + if (pretty_print_) + json_string_->append(" "); + json_string_->append("]"); + break; + } + + case Value::TYPE_DICTIONARY: + { + json_string_->append("{"); + if (pretty_print_) + json_string_->append(kPrettyPrintLineEnding); + + const DictionaryValue* dict = + static_cast<const DictionaryValue*>(node); + for (DictionaryValue::key_iterator key_itr = dict->begin_keys(); + key_itr != dict->end_keys(); + ++key_itr) { + + if (key_itr != dict->begin_keys()) { + json_string_->append(","); + if (pretty_print_) + json_string_->append(kPrettyPrintLineEnding); + } + + Value* value = NULL; + bool result = dict->Get(*key_itr, &value); + DCHECK(result); + + if (pretty_print_) + IndentLine(depth + 1); + AppendQuotedString(*key_itr); + if (pretty_print_) { + json_string_->append(": "); + } else { + json_string_->append(":"); + } + BuildJSONString(value, depth + 1); + } + + if (pretty_print_) { + json_string_->append(kPrettyPrintLineEnding); + IndentLine(depth); + json_string_->append("}"); + } else { + json_string_->append("}"); + } + break; + } + + default: + // TODO(jhughes): handle TYPE_BINARY + NOTREACHED() << "unknown json type"; + } +} + +void JSONWriter::AppendQuotedString(const std::wstring& str) { + string_escape::JavascriptDoubleQuote(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, ' ')); +} diff --git a/base/json_writer.h b/base/json_writer.h new file mode 100644 index 0000000..9f8f8d3 --- /dev/null +++ b/base/json_writer.h @@ -0,0 +1,71 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_COMMON_JSON_WRITER_H__ +#define CHROME_COMMON_JSON_WRITER_H__ + +#include <string> + +#include "base/basictypes.h" + +class Value; + +class JSONWriter { + public: + // Given a root node, generates a JSON string and puts it into |json|. + // If |pretty_print| is true, return a slightly nicer formated json string + // (pads with whitespace to help readability). If |pretty_print| is false, + // we try to generate as compact a string as possible. + // 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, bool pretty_print, + std::string* json); + + private: + JSONWriter(bool pretty_print, std::string* json); + DISALLOW_EVIL_CONSTRUCTORS(JSONWriter); + + // 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 str to json_string_. + void AppendQuotedString(const std::wstring& str); + + // Adds space to json_string_ for the indent level. + void IndentLine(int depth); + + // Where we write JSON data as we generate it. + std::string* json_string_; + + bool pretty_print_; +}; + +#endif // CHROME_COMMON_JSON_WRITER_H__ diff --git a/base/json_writer_unittest.cc b/base/json_writer_unittest.cc new file mode 100644 index 0000000..4cbe2d0 --- /dev/null +++ b/base/json_writer_unittest.cc @@ -0,0 +1,82 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/json_writer.h" +#include "base/values.h" + +TEST(JSONWriterTest, Writing) { + // Test null + Value* root = Value::CreateNullValue(); + std::string output_js; + JSONWriter::Write(root, false, &output_js); + ASSERT_EQ("null", output_js); + delete root; + + // Test empty dict + root = new DictionaryValue; + JSONWriter::Write(root, false, &output_js); + ASSERT_EQ("{}", output_js); + delete root; + + // Test empty list + root = new ListValue; + JSONWriter::Write(root, false, &output_js); + ASSERT_EQ("[]", output_js); + delete root; + + // Test Real values should always have a decimal or an 'e'. + root = Value::CreateRealValue(1.0); + JSONWriter::Write(root, false, &output_js); + ASSERT_EQ("1.0", 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(L"list", list); + DictionaryValue* inner_dict = new DictionaryValue; + list->Append(inner_dict); + inner_dict->SetInteger(L"inner int", 10); + ListValue* inner_list = new ListValue; + list->Append(inner_list); + list->Append(Value::CreateBooleanValue(true)); + + JSONWriter::Write(&root_dict, false, &output_js); + ASSERT_EQ("{\"list\":[{\"inner int\":10},[],true]}", output_js); + JSONWriter::Write(&root_dict, true, &output_js); + ASSERT_EQ("{\r\n" + " \"list\": [ {\r\n" + " \"inner int\": 10\r\n" + " }, [ ], true ]\r\n" + "}\r\n", + output_js); +} + diff --git a/base/linked_ptr.h b/base/linked_ptr.h new file mode 100644 index 0000000..9204d54 --- /dev/null +++ b/base/linked_ptr.h @@ -0,0 +1,200 @@ +// linked_ptr.h +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// 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_LINKED_PTR_H_ +#define BASE_LINKED_PTR_H_ + +#include "base/logging.h" // for CHECK macros + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr<Superclass>(obj) vs linked_ptr<Subclass>(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr<T>. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Join an existing circle. + void join(linked_ptr_internal const* ptr) { + next_ = ptr->next_; + ptr->next_ = this; + } + + // Leave whatever circle we're part of. Returns true iff we were the + // last member of the circle. Once this is done, you can join() another. + bool depart() { + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template <typename T> +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template <typename U> linked_ptr(linked_ptr<U> const& ptr) { copy(&ptr); } + linked_ptr(linked_ptr const& ptr) { DCHECK_NE(&ptr, this); copy(&ptr); } + + // Assignment releases the old value and acquires the new. + template <typename U> linked_ptr& operator=(linked_ptr<U> const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { depart(); capture(ptr); } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + // Release ownership of the pointed object and returns it. + // Sole ownership by this linked_ptr object is required. + T* release() { + bool last = link_.depart(); + CHECK(last); + T* v = value_; + value_ = NULL; + return v; + } + + bool operator==(T* p) const { return value_ == p; } + bool operator!=(T* p) const { return value_ != p; } + template <typename U> + bool operator==(linked_ptr<U> const& ptr) const { + return value_ == ptr.get(); + } + template <typename U> + bool operator!=(linked_ptr<U> const& ptr) const { + return value_ != ptr.get(); + } + + private: + template <typename U> + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template <typename U> void copy(linked_ptr<U> const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template<typename T> inline +bool operator==(T* ptr, const linked_ptr<T>& x) { + return ptr == x.get(); +} + +template<typename T> inline +bool operator!=(T* ptr, const linked_ptr<T>& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr<T> +// Doing e.g. make_linked_ptr(new FooBarBaz<type>(arg)) is a shorter notation +// for linked_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg)) +template <typename T> +linked_ptr<T> make_linked_ptr(T* ptr) { + return linked_ptr<T>(ptr); +} + +#endif // BASE_LINKED_PTR_H_ diff --git a/base/linked_ptr_unittest.cc b/base/linked_ptr_unittest.cc new file mode 100644 index 0000000..c749ce9 --- /dev/null +++ b/base/linked_ptr_unittest.cc @@ -0,0 +1,136 @@ +// linked_ptr_unittest.cc +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string> +#include <iostream> + +#include "base/linked_ptr.h" + +#include "base/string_util.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 += StringPrintf("A%d ctor\n", mynum); } + virtual ~A() { history += StringPrintf("A%d dtor\n", mynum); } + virtual void Use() { history += StringPrintf("A%d use\n", mynum); } + int mynum; +}; + +// Subclass +struct B: public A { + B() { history += StringPrintf("B%d ctor\n", mynum); } + ~B() { history += StringPrintf("B%d dtor\n", mynum); } + virtual void Use() { history += StringPrintf("B%d use\n", mynum); } +}; + +} // namespace + +TEST(LinkedPtrTest, Test) { + { + linked_ptr<A> a0, a1, a2; + a0 = a0; + a1 = a2; + ASSERT_EQ(a0.get(), static_cast<A*>(NULL)); + ASSERT_EQ(a1.get(), static_cast<A*>(NULL)); + ASSERT_EQ(a2.get(), static_cast<A*>(NULL)); + ASSERT_TRUE(a0 == NULL); + ASSERT_TRUE(a1 == NULL); + ASSERT_TRUE(a2 == NULL); + + { + linked_ptr<A> a3(new A); + a0 = a3; + ASSERT_TRUE(a0 == a3); + ASSERT_TRUE(a0 != NULL); + ASSERT_TRUE(a0.get() == a3); + ASSERT_TRUE(a0 == a3.get()); + linked_ptr<A> a4(a0); + a1 = a4; + linked_ptr<A> a5(new A); + ASSERT_TRUE(a5.get() != a3); + ASSERT_TRUE(a5 != a3.get()); + a2 = a5; + linked_ptr<B> b0(new B); + linked_ptr<A> a6(b0); + ASSERT_TRUE(b0 == a6); + ASSERT_TRUE(a6 == b0); + ASSERT_TRUE(b0 != NULL); + a5 = b0; + a5 = b0; + a3->Use(); + a4->Use(); + a5->Use(); + a6->Use(); + b0->Use(); + (*b0).Use(); + b0.get()->Use(); + } + + a0->Use(); + a1->Use(); + a2->Use(); + + a1 = a2; + a2.reset(new A); + a0.reset(); + + linked_ptr<A> a7; + } + + ASSERT_EQ(history, + "A0 ctor\n" + "A1 ctor\n" + "A2 ctor\n" + "B2 ctor\n" + "A0 use\n" + "A0 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 dtor\n" + "A2 dtor\n" + "A0 use\n" + "A0 use\n" + "A1 use\n" + "A3 ctor\n" + "A0 dtor\n" + "A3 dtor\n" + "A1 dtor\n" + ); +} diff --git a/base/lock.cc b/base/lock.cc new file mode 100644 index 0000000..afa1a6f --- /dev/null +++ b/base/lock.cc @@ -0,0 +1,156 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Provide place to put profiling methods for the +// Lock class. +// The Lock class is used everywhere, and hence any changes +// to lock.h tend to require a complete rebuild. To facilitate +// profiler development, all the profiling methods are listed +// here. Note that they are only instantiated in a debug +// build, and the header provides all the trivial implementations +// in a production build. + +#include "base/lock.h" +#include "base/logging.h" + +Lock::Lock() + : lock_() + , recursion_count_shadow_(0) { +#ifndef NDEBUG + recursion_used_ = false; + acquisition_count_ = 0; + contention_count_ = 0; +#endif +} + +Lock::~Lock() { +#ifndef NDEBUG + // There should be no one to contend for the lock, + // ...but we need the memory barrier to get a good value. + lock_.Lock(); + int final_recursion_count = recursion_count_shadow_; + lock_.Unlock(); +#endif + + // Allow unit test exception only at end of method. +#ifndef NDEBUG + DCHECK(0 == final_recursion_count); +#endif +} + +void Lock::Acquire() { +#ifdef NDEBUG + lock_.Lock(); + recursion_count_shadow_++; +#else // NDEBUG + if (!lock_.Try()) { + // We have contention. + lock_.Lock(); + contention_count_++; + } + // ONLY access data after locking. + recursion_count_shadow_++; + if (1 == recursion_count_shadow_) + acquisition_count_++; + else if (2 == recursion_count_shadow_ && !recursion_used_) + // Usage Note: Set a break point to debug. + recursion_used_ = true; +#endif // NDEBUG +} + +void Lock::Release() { + --recursion_count_shadow_; // ONLY access while lock is still held. +#ifndef NDEBUG + DCHECK(0 <= recursion_count_shadow_); +#endif + lock_.Unlock(); +} + +bool Lock::Try() { + if (lock_.Try()) { + recursion_count_shadow_++; +#ifndef NDEBUG + if (1 == recursion_count_shadow_) + acquisition_count_++; + else if (2 == recursion_count_shadow_ && !recursion_used_) + // Usage Note: Set a break point to debug. + recursion_used_ = true; +#endif + return true; + } else { + return false; + } +} + +// GetCurrentThreadRecursionCount returns the number of nested Acquire() calls +// that have been made by the current thread holding this lock. The calling +// thread is ***REQUIRED*** to be *currently* holding the lock. If that +// calling requirement is violated, the return value is not well defined. +// Return results are guaranteed correct if the caller has acquired this lock. +// The return results might be incorrect otherwise. +// This method is designed to be fast in non-debug mode by co-opting +// synchronization using lock_ (no additional synchronization is used), but in +// debug mode it slowly and carefully validates the requirement (and fires a +// a DCHECK if it was called incorrectly). +int32 Lock::GetCurrentThreadRecursionCount() { +#ifndef NDEBUG + // If this DCHECK fails, then the most probable cause is: + // This method was called by class AutoUnlock during processing of a + // Wait() call made into the ConditonVariable class. That call to + // Wait() was made (incorrectly) without first Aquiring this Lock + // instance. + lock_.Lock(); + int temp = recursion_count_shadow_; + lock_.Unlock(); + // Unit tests catch an exception, so we need to be careful to test + // outside the critical section, since the Leave would be skipped!?! + DCHECK(temp >= 1); // Allow unit test exception only at end of method. +#endif // DEBUG + + // We hold lock, so this *is* correct value. + return recursion_count_shadow_; +} + + +AutoUnlock::AutoUnlock(Lock& lock) : lock_(&lock), release_count_(0) { + // We require our caller have the lock, so we can call for recursion count. + // CRITICALLY: Fetch value before we release the lock. + int32 count = lock_->GetCurrentThreadRecursionCount(); + DCHECK(count > 0); // Make sure we owned the lock. + while (count-- > 0) { + release_count_++; + lock_->Release(); + } +} + +AutoUnlock::~AutoUnlock() { + DCHECK(release_count_ >= 0); + while (release_count_-- > 0) + lock_->Acquire(); +} diff --git a/base/lock.h b/base/lock.h new file mode 100644 index 0000000..9613da0 --- /dev/null +++ b/base/lock.h @@ -0,0 +1,122 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_LOCK_H__ +#define BASE_LOCK_H__ + +#include "base/lock_impl.h" + +// A convenient wrapper for a critical section. +// +// NOTE: A thread may acquire the same lock multiple times, but it must call +// Release for each call to Acquire in order to finally release the lock. +// +// Complication: UnitTest for DeathTests catch DCHECK exceptions, so we need +// to write code assuming DCHECK will throw. This means we need to save any +// assertable value in a local until we can safely throw. +class Lock { + public: + Lock(); + ~Lock(); + void Acquire(); + void Release(); + // 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(); + + private: + LockImpl lock_; // User-supplied underlying lock implementation. + + // All private data is implicitly protected by spin_lock_. + // Be VERY careful to only access under that lock. + int32 recursion_count_shadow_; + + // Allow access to GetCurrentThreadRecursionCount() + friend class AutoUnlock; + int32 GetCurrentThreadRecursionCount(); + +#ifndef NDEBUG + // Even in Debug mode, the expensive tallies won't be calculated by default. + bool recursion_used_; + int32 acquisition_count_; + + int32 contention_count_; +#endif // NDEBUG + + DISALLOW_EVIL_CONSTRUCTORS(Lock); +}; + +// A helper class that acquires the given Lock while the AutoLock is in scope. +class AutoLock { + public: + AutoLock(Lock& lock) : lock_(lock) { + lock_.Acquire(); + } + + ~AutoLock() { + lock_.Release(); + } + + private: + Lock& lock_; + DISALLOW_EVIL_CONSTRUCTORS(AutoLock); +}; + +// A helper macro to perform a single operation (expressed by expr) +// in a lock +#define LOCKED_EXPRESSION(lock, expr) \ + do { \ + AutoLock _auto_lock(lock); \ + (expr); \ + } while (0) + +// AutoUnlock is a helper class for ConditionVariable instances +// that is analogous to AutoLock. It provides for nested Releases +// of a lock for the Wait functionality of a ConditionVariable class. +// The destructor automatically does the corresponding Acquire +// calls (to return to the initial nested lock state). + +// Instances of AutoUnlock can ***ONLY*** validly be constructed if the +// caller currently holds the lock provided as the constructor's argument. +// If that ***REQUIREMENT*** is violated in debug mode, a DCHECK will +// be generated in the Lock class. In production (non-debug), +// the results are undefined (and probably bad) if the caller +// is not already holding the indicated lock. +class ConditionVariable; +class AutoUnlock { + private: // Everything is private, so only our friend can use us. + friend class ConditionVariable; // The only user of this class. + explicit AutoUnlock(Lock& lock); + ~AutoUnlock(); + + Lock* lock_; + int release_count_; +}; + +#endif // BASE_LOCK_H__ diff --git a/base/lock_impl.h b/base/lock_impl.h new file mode 100644 index 0000000..331bdc6 --- /dev/null +++ b/base/lock_impl.h @@ -0,0 +1,87 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_LOCK_IMPL_H__ +#define BASE_LOCK_IMPL_H__ + +#include "base/basictypes.h" + +#if defined(WIN32) +#include <Windows.h> +#elif defined(__APPLE__) +#include <libkern/OSAtomic.h> +#endif + +// 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 LockImpl { + public: + 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(); + + private: +#if defined(WIN32) + CRITICAL_SECTION critical_section_; +#elif defined(__APPLE__) + OSSpinLock spin_lock_; +#endif + + DISALLOW_EVIL_CONSTRUCTORS(LockImpl); +}; + +class AutoLockImpl { + public: + AutoLockImpl(LockImpl* lock_impl) + : lock_impl_(lock_impl) { + lock_impl_->Lock(); + } + + ~AutoLockImpl() { + lock_impl_->Unlock(); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(AutoLockImpl); + + LockImpl* lock_impl_; +}; + +#endif // BASE_LOCK_IMPL_H__ diff --git a/base/lock_impl_mac.cc b/base/lock_impl_mac.cc new file mode 100644 index 0000000..fcb630d --- /dev/null +++ b/base/lock_impl_mac.cc @@ -0,0 +1,49 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/lock_impl.h" + +LockImpl::LockImpl() + : spin_lock_(0) { +} + +LockImpl::~LockImpl() { +} + +bool LockImpl::Try() { + return ::OSSpinLockTry(&spin_lock_); +} + +void LockImpl::Lock() { + ::OSSpinLockLock(&spin_lock_); +} + +void LockImpl::Unlock() { + ::OSSpinLockUnlock(&spin_lock_); +} diff --git a/base/lock_impl_win.cc b/base/lock_impl_win.cc new file mode 100644 index 0000000..a45cbdd --- /dev/null +++ b/base/lock_impl_win.cc @@ -0,0 +1,50 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/lock_impl.h" + +LockImpl::LockImpl() { + ::InitializeCriticalSection(&critical_section_); +} + +LockImpl::~LockImpl() { + ::DeleteCriticalSection(&critical_section_); +} + +bool LockImpl::Try() { + return ::TryEnterCriticalSection(&critical_section_) != FALSE; +} + +void LockImpl::Lock() { + ::EnterCriticalSection(&critical_section_); +} + +void LockImpl::Unlock() { + ::LeaveCriticalSection(&critical_section_); +} diff --git a/base/logging.cc b/base/logging.cc new file mode 100644 index 0000000..eba177e --- /dev/null +++ b/base/logging.cc @@ -0,0 +1,396 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <ctime> +#include <iomanip> +#include <cstring> +#include <windows.h> +#include <algorithm> +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/lock_impl.h" +#include "base/logging.h" + +namespace logging { + +bool g_enable_dcheck = false; + +const char* const log_severity_names[LOG_NUM_SEVERITIES] = { + "INFO", "WARNING", "ERROR", "FATAL" }; + +int min_log_level = 0; +LogLockingState lock_log_file = LOCK_LOG_FILE; +LoggingDestination logging_destination = LOG_ONLY_TO_FILE; + +const int kMaxFilteredLogLevel = LOG_WARNING; +char* log_filter_prefix = NULL; + +// which log file to use? This is initialized by InitLogging or +// will be lazily initialized to the default value when it is +// first needed. +wchar_t log_file_name[MAX_PATH] = { 0 }; + +// this file is lazily opened and the handle may be NULL +HANDLE 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; + +// An assert handler override specified by the client to be called instead of +// the debug message dialog. +LogAssertHandlerFunction log_assert_handler = NULL; + +// 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 LockImpl* log_lock = NULL; + +// When we don't use a lock, we are using a global mutex. We need to do this +// because LockFileEx is not thread safe. +HANDLE log_mutex = NULL; + +// 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[0]) { + // nobody has called InitLogging to specify a debug log file, so here we + // initialize the log file name to the default + GetModuleFileName(NULL, log_file_name, MAX_PATH); + wchar_t* last_backslash = wcsrchr(log_file_name, '\\'); + if (last_backslash) + last_backslash[1] = 0; // name now ends with the backslash + wcscat_s(log_file_name, L"debug.log"); + } + + log_file = CreateFile(log_file_name, 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); + return true; +} + +void InitLogMutex() { + if (!log_mutex) { + // \ is not a legal character in mutex names so we replace \ with / + std::wstring safe_name(log_file_name); + 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()); + } +} + +void InitLogging(const wchar_t* new_log_file, LoggingDestination logging_dest, + LogLockingState lock_log, OldFileDeletionState delete_old) { + g_enable_dcheck = CommandLine().HasSwitch(switches::kEnableDCHECK); + + if (log_file) { + // calling InitLogging twice or after some log call has already opened the + // default log file will re-initialize to the new options + CloseHandle(log_file); + log_file = NULL; + } + + lock_log_file = lock_log; + logging_destination = logging_dest; + + // ignore file options if logging is disabled or only to system + if (logging_destination == LOG_NONE || + logging_destination == LOG_ONLY_TO_SYSTEM_DEBUG_LOG) + return; + + wcscpy_s(log_file_name, MAX_PATH, new_log_file); + if (delete_old == DELETE_OLD_LOG_FILE) + DeleteFile(log_file_name); + + if (lock_log_file == LOCK_LOG_FILE) { + InitLogMutex(); + } else if (!log_lock) { + log_lock = new LockImpl(); + } + + InitializeLogFileHandle(); +} + +void SetMinLogLevel(int level) { + min_log_level = level; +} + +int GetMinLogLevel() { + return min_log_level; +} + +void SetLogFilterPrefix(const char* filter) { + if (log_filter_prefix) { + delete[] log_filter_prefix; + log_filter_prefix = NULL; + } + + if (filter) { + size_t size = strlen(filter)+1; + log_filter_prefix = new char[size]; + strcpy_s(log_filter_prefix, size, filter); + } +} + +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 SetLogAssertHandler(LogAssertHandlerFunction handler) { + log_assert_handler = handler; +} + +// Displays a message box to the user with the error message in it. 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. +void DisplayDebugMessage(const std::string& str) { + if (str.empty()) + return; + + // look for the debug dialog program next to our application + 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"); + + // stupid CreateProcess requires a non-const command line and may modify it. + // We also want to use the wide string + int charcount = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0); + if (!charcount) + return; + scoped_array<wchar_t> cmdline(new wchar_t[charcount]); + if (!MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, cmdline.get(), charcount)) + 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.get(), 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.get(), L"Fatal error", + MB_OK | MB_ICONHAND | MB_TOPMOST); + } +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + int ctr) + : severity_(severity) { + Init(file, line); +} + +LogMessage::LogMessage(const char* file, int line, const CheckOpString& result) + : severity_(LOG_FATAL) { + Init(file, line); + stream_ << "Check failed: " << (*result.str_); +} + +LogMessage::LogMessage(const char* file, int line) + : severity_(LOG_INFO) { + Init(file, line); +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity) + : severity_(severity) { + Init(file, line); +} + +// writes the common header info to the stream +void LogMessage::Init(const char* file, int line) { + // log only the filename + const char* last_slash = strrchr(file, '\\'); + if (last_slash) + file = last_slash + 1; + + // TODO(darin): It might be nice if the columns were fixed width. + + stream_ << '['; + if (log_process_id) + stream_ << GetCurrentProcessId() << ':'; + if (log_thread_id) + stream_ << GetCurrentThreadId() << ':'; + if (log_timestamp) { + time_t t = time(NULL); +#if _MSC_VER >= 1400 + struct tm local_time = {0}; + localtime_s(&local_time, &t); + struct tm* tm_time = &local_time; +#else + struct tm* tm_time = localtime(&t); +#endif + stream_ << std::setfill('0') + << std::setw(2) << 1 + tm_time->tm_mon + << std::setw(2) << tm_time->tm_mday + << '/' + << std::setw(2) << tm_time->tm_hour + << std::setw(2) << tm_time->tm_min + << std::setw(2) << tm_time->tm_sec + << ':'; + } + if (log_tickcount) + stream_ << GetTickCount() << ':'; + stream_ << log_severity_names[severity_] << ":" << file << "(" << line << ")] "; + + message_start_ = stream_.tellp(); +} + +LogMessage::~LogMessage() { + // TODO(brettw) modify the macros so that nothing is executed when the log + // level is too high. + if (severity_ < min_log_level) + return; + + std::string str_newline(stream_.str()); + str_newline.append("\r\n"); + + if (log_filter_prefix && severity_ <= kMaxFilteredLogLevel && + str_newline.compare(message_start_, strlen(log_filter_prefix), + log_filter_prefix) != 0) { + return; + } + + if (logging_destination == LOG_ONLY_TO_SYSTEM_DEBUG_LOG || + logging_destination == LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG) + OutputDebugStringA(str_newline.c_str()); + + // write to log file + if (logging_destination != LOG_NONE && + logging_destination != LOG_ONLY_TO_SYSTEM_DEBUG_LOG && + InitializeLogFileHandle()) { + // we can have multiple threads and/or processes, so try to prevent them from + // clobbering each other's writes + if (lock_log_file == LOCK_LOG_FILE) { + // Ensure that the mutex is initialized in case the client app did not + // call InitLogging. This is not thread safe. See below + InitLogMutex(); + + DWORD r = ::WaitForSingleObject(log_mutex, INFINITE); + DCHECK(r != WAIT_ABANDONED); + } else { + // use the lock + if (!log_lock) { + // The client app did not call InitLogging, and so the lock has not + // been created. 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. + log_lock = new LockImpl(); + } + log_lock->Lock(); + } + + SetFilePointer(log_file, 0, 0, SEEK_END); + DWORD num_written; + WriteFile(log_file, (void*)str_newline.c_str(), (DWORD)str_newline.length(), &num_written, NULL); + + if (lock_log_file == LOCK_LOG_FILE) { + ReleaseMutex(log_mutex); + } else { + log_lock->Unlock(); + } + } + + if (severity_ == LOG_FATAL) { + // display a message or break into the debugger on a fatal error + if (::IsDebuggerPresent()) { + __debugbreak(); + } 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 + DisplayDebugMessage(stream_.str()); + // Crash the process to generate a dump. + __debugbreak(); + } + } + } +} + +void CloseLogFile() { + if (!log_file) + return; + + CloseHandle(log_file); + log_file = NULL; +} + +} // namespace logging + +std::ostream& operator<<(std::ostream& out, const wchar_t* wstr) { + if (!wstr || !wstr[0]) + return out; + + // compute the length of the buffer we'll need + int charcount = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, + NULL, 0, NULL, NULL); + if (charcount == 0) + return out; + + // convert + scoped_array<char> buf(new char[charcount]); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buf.get(), charcount, NULL, NULL); + return out << buf.get(); +} diff --git a/base/logging.h b/base/logging.h new file mode 100644 index 0000000..615b1e0 --- /dev/null +++ b/base/logging.h @@ -0,0 +1,523 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_LOGGING_H__ +#define BASE_LOGGING_H__ + +#include <string> +#include <cstring> +#include <sstream> + +#include "base/basictypes.h" +#include "base/scoped_ptr.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(<a particular severity level>). E.g., +// +// LOG(INFO) << "Found " << num_cookies << " cookies"; +// +// You can also do conditional logging: +// +// LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; +// +// The above will cause log messages to be output on the 1st, 11th, 21st, ... +// times it is executed. Note that the special COUNTER value is used to +// identify which repetition is happening. +// +// The CHECK(condition) macro is active in both debug and release builds and +// effectively performs a LOG(FATAL) which terminates the process and +// generates a crashdump unless a debugger is attached. +// +// There are also "debug mode" logging macros like the ones above: +// +// DLOG(INFO) << "Found cookies"; +// +// DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; +// +// All "debug mode" logging is compiled away to nothing for non-debug mode +// compiles. LOG_IF and development flags also work well together +// because the code can be compiled away sometimes. +// +// We also have +// +// LOG_ASSERT(assertion); +// DLOG_ASSERT(assertion); +// +// which is syntactic sugar for {,D}LOG_IF(FATAL, assert fails) << assertion; +// +// We also override the standard 'assert' to use 'DLOG_ASSERT'. +// +// The supported severity levels for macros that allow you to specify one +// are (in increasing order of severity) INFO, WARNING, ERROR, and FATAL. +// +// There is also the special severity of DFATAL, which logs FATAL in +// debug mode, ERROR in normal mode. +// +// Very important: logging a message at the FATAL severity level causes +// the program to terminate (after the message is logged). + +namespace logging { + +// Where to record logging output? A flat file and/or system debug log via +// OutputDebugString. Defaults to LOG_ONLY_TO_FILE. +enum LoggingDestination { LOG_NONE, + LOG_ONLY_TO_FILE, + LOG_ONLY_TO_SYSTEM_DEBUG_LOG, + LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG }; + +// Indicates that the log file should be locked when being written to. +// Often, there is no locking, which is fine for a single threaded program. +// If logging is being done from multiple threads or there can be more than +// one process doing the logging, the file should be locked during writes to +// make each log outut atomic. Other writers will block. +// +// All processes writing to the log file must have their locking set for it to +// work properly. Defaults to DONT_LOCK_LOG_FILE. +enum LogLockingState { LOCK_LOG_FILE, DONT_LOCK_LOG_FILE }; + +// On startup, should we delete or append to an existing log file (if any)? +// Defaults to APPEND_TO_OLD_LOG_FILE. +enum OldFileDeletionState { DELETE_OLD_LOG_FILE, APPEND_TO_OLD_LOG_FILE }; + +// 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. +#if defined(WIN32) +void InitLogging(const wchar_t* log_file, LoggingDestination logging_dest, + LogLockingState lock_log, OldFileDeletionState delete_old); +#else +// TODO(avi): do we want to do a unification of character types here? +void InitLogging(const char* log_file, LoggingDestination logging_dest, + LogLockingState lock_log, OldFileDeletionState delete_old); +#endif + +// 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) +// if this function is not called. +void SetMinLogLevel(int level); + +// Gets the curreng log level. +int GetMinLogLevel(); + +// Sets the log filter prefix. Any log message below LOG_ERROR severity that +// doesn't start with this prefix with be silently ignored. The filter defaults +// to NULL (everything is logged) if this function is not called. Messages +// with severity of LOG_ERROR or higher will not be filtered. +void SetLogFilterPrefix(const char* filter); + +// 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. +void SetLogItems(bool enable_process_id, bool enable_thread_id, + bool enable_timestamp, bool enable_tickcount); + +// Sets the Log Assert Handler that will be used to notify of check failures. +// The default handler shows a dialog box, 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); +void SetLogAssertHandler(LogAssertHandlerFunction handler); + +typedef int LogSeverity; +const LogSeverity LOG_INFO = 0; +const LogSeverity LOG_WARNING = 1; +const LogSeverity LOG_ERROR = 2; +const LogSeverity LOG_FATAL = 3; +const LogSeverity LOG_NUM_SEVERITIES = 4; + +// LOG_DFATAL_LEVEL is LOG_FATAL in debug mode, ERROR in normal mode +#ifdef NDEBUG +const LogSeverity LOG_DFATAL_LEVEL = LOG_ERROR; +#else +const LogSeverity LOG_DFATAL_LEVEL = 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_INFO \ + logging::LogMessage(__FILE__, __LINE__) +#define COMPACT_GOOGLE_LOG_WARNING \ + logging::LogMessage(__FILE__, __LINE__, logging::LOG_WARNING) +#define COMPACT_GOOGLE_LOG_ERROR \ + logging::LogMessage(__FILE__, __LINE__, logging::LOG_ERROR) +#define COMPACT_GOOGLE_LOG_FATAL \ + logging::LogMessage(__FILE__, __LINE__, logging::LOG_FATAL) +#define COMPACT_GOOGLE_LOG_DFATAL \ + logging::LogMessage(__FILE__, __LINE__, logging::LOG_DFATAL_LEVEL) + +// 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_0 \ + logging::LogMessage(__FILE__, __LINE__, logging::LOG_ERROR) + +// 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(severity) COMPACT_GOOGLE_LOG_ ## severity.stream() +#define SYSLOG(severity) LOG(severity) + +#define LOG_IF(severity, condition) \ + !(condition) ? (void) 0 : logging::LogMessageVoidify() & LOG(severity) +#define SYSLOG_IF(severity, condition) LOG_IF(severity, condition) + +#define LOG_ASSERT(condition) \ + LOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". " +#define SYSLOG_ASSERT(condition) \ + SYSLOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". " + +// 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. +#define CHECK(condition) \ + LOG_IF(FATAL, !(condition)) << "Check failed: " #condition ". " + +// A container for a string pointer which can be evaluated to a bool - +// true iff the pointer is NULL. +struct CheckOpString { + CheckOpString(std::string* str) : str_(str) { } + // No destructor: if str_ is non-NULL, we're about to LOG(FATAL), + // so there's no point in cleaning up str_. + operator bool() const { return str_ != NULL; } + std::string* str_; +}; + +// 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. +template<class t1, class t2> +std::string* MakeCheckOpString(const t1& v1, const t2& v2, const char* names) { + std::ostringstream ss; + ss << names << " (" << v1 << " vs. " << v2 << ")"; + std::string* msg = new std::string(ss.str()); + return msg; +} + +extern std::string* MakeCheckOpStringIntInt(int v1, int v2, const char* names); + +template<int, int> +std::string* MakeCheckOpString(const int& v1, const int& v2, const char* names) { + return MakeCheckOpStringIntInt(v1, v2, names); +} + +// Plus some debug-logging macros that get compiled to nothing for production +// +// DEBUG_MODE is for uses like +// if (DEBUG_MODE) foo.CheckThatFoo(); +// instead of +// #ifndef NDEBUG +// foo.CheckThatFoo(); +// #endif + +#ifndef NDEBUG + +#define DLOG(severity) LOG(severity) +#define DLOG_IF(severity, condition) LOG_IF(severity, condition) +#define DLOG_ASSERT(condition) LOG_ASSERT(condition) + +// debug-only checking. not executed in NDEBUG mode. +enum { DEBUG_MODE = 1 }; +#define DCHECK(condition) \ + LOG_IF(FATAL, !(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 (logging::CheckOpString _result = \ + logging::Check##name##Impl((val1), (val2), #val1 " " #op " " #val2)) \ + logging::LogMessage(__FILE__, __LINE__, _result).stream() + +// Helper functions for string comparisons. +// To avoid bloat, the definitions are in logging.cc. +#define DECLARE_DCHECK_STROP_IMPL(func, expected) \ + std::string* Check##func##expected##Impl(const char* s1, \ + const char* s2, \ + const char* names); +DECLARE_DCHECK_STROP_IMPL(strcmp, true) +DECLARE_DCHECK_STROP_IMPL(strcmp, false) +DECLARE_DCHECK_STROP_IMPL(_stricmp, true) +DECLARE_DCHECK_STROP_IMPL(_stricmp, false) +#undef DECLARE_DCHECK_STROP_IMPL + +// Helper macro for string comparisons. +// Don't use this macro directly in your code, use CHECK_STREQ et al below. +#define DCHECK_STROP(func, op, expected, s1, s2) \ + while (CheckOpString _result = \ + logging::Check##func##expected##Impl((s1), (s2), \ + #s1 " " #op " " #s2)) \ + LOG(FATAL) << *_result.str_ + +// String (char*) equality/inequality checks. +// CASE versions are case-insensitive. +// +// Note that "s1" and "s2" may be temporary strings which are destroyed +// by the compiler at the end of the current "full expression" +// (e.g. DCHECK_STREQ(Foo().c_str(), Bar().c_str())). + +#define DCHECK_STREQ(s1, s2) DCHECK_STROP(strcmp, ==, true, s1, s2) +#define DCHECK_STRNE(s1, s2) DCHECK_STROP(strcmp, !=, false, s1, s2) +#define DCHECK_STRCASEEQ(s1, s2) DCHECK_STROP(_stricmp, ==, true, s1, s2) +#define DCHECK_STRCASENE(s1, s2) DCHECK_STROP(_stricmp, !=, false, s1, s2) + +#define DCHECK_INDEX(I,A) DCHECK(I < (sizeof(A)/sizeof(A[0]))) +#define DCHECK_BOUND(B,A) DCHECK(B <= (sizeof(A)/sizeof(A[0]))) + +#else // NDEBUG + +#define DLOG(severity) \ + true ? (void) 0 : logging::LogMessageVoidify() & LOG(severity) + +#define DLOG_IF(severity, condition) \ + true ? (void) 0 : logging::LogMessageVoidify() & LOG(severity) + +#define DLOG_ASSERT(condition) \ + true ? (void) 0 : LOG_ASSERT(condition) + +enum { DEBUG_MODE = 0 }; + +// This macro can be followed by a sequence of stream parameters in +// non-debug mode. The DCHECK and friends macros use this so that +// the expanded expression DCHECK(foo) << "asdf" is still syntactically +// valid, even though the expression will get optimized away. +#define NDEBUG_EAT_STREAM_PARAMETERS \ + logging::LogMessage(__FILE__, __LINE__).stream() + +// Set to true in InitLogging when we want to enable the dchecks in release. +extern bool g_enable_dcheck; +#define DCHECK(condition) \ + !logging::g_enable_dcheck ? void (0) : \ + LOG_IF(FATAL, !(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 (logging::g_enable_dcheck) \ + if (logging::CheckOpString _result = \ + logging::Check##name##Impl((val1), (val2), #val1 " " #op " " #val2)) \ + logging::LogMessage(__FILE__, __LINE__, _result).stream() + +#define DCHECK_STREQ(str1, str2) \ + while (false) NDEBUG_EAT_STREAM_PARAMETERS + +#define DCHECK_STRCASEEQ(str1, str2) \ + while (false) NDEBUG_EAT_STREAM_PARAMETERS + +#define DCHECK_STRNE(str1, str2) \ + while (false) NDEBUG_EAT_STREAM_PARAMETERS + +#define DCHECK_STRCASENE(str1, str2) \ + while (false) NDEBUG_EAT_STREAM_PARAMETERS + +#endif // NDEBUG + +// Helper functions for DCHECK_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_DCHECK_OP_IMPL(name, op) \ + template <class t1, class t2> \ + inline std::string* Check##name##Impl(const t1& v1, const t2& v2, \ + const char* names) { \ + if (v1 op v2) return NULL; \ + else return MakeCheckOpString(v1, v2, names); \ + } \ + inline std::string* Check##name##Impl(int v1, int v2, const char* names) { \ + if (v1 op v2) return NULL; \ + else return MakeCheckOpString(v1, v2, names); \ + } +DEFINE_DCHECK_OP_IMPL(EQ, ==) +DEFINE_DCHECK_OP_IMPL(NE, !=) +DEFINE_DCHECK_OP_IMPL(LE, <=) +DEFINE_DCHECK_OP_IMPL(LT, < ) +DEFINE_DCHECK_OP_IMPL(GE, >=) +DEFINE_DCHECK_OP_IMPL(GT, > ) +#undef DEFINE_DCHECK_OP_IMPL + +// Equality/Inequality checks - compare two values, and log a LOG_FATAL 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 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. + // Implied severity = LOG_FATAL + LogMessage(const char* file, int line, const CheckOpString& result); + + ~LogMessage(); + + std::ostream& stream() { return stream_; } + + private: + void Init(const char* file, int line); + + LogSeverity severity_; + std::ostringstream stream_; + int message_start_; // offset of the start of the message (past prefix info). + + DISALLOW_EVIL_CONSTRUCTORS(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&) { } +}; + +// 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. +void CloseLogFile(); + +} // 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. +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(); +} + +#endif // BASE_LOGGING_H__ diff --git a/base/md5.cc b/base/md5.cc new file mode 100644 index 0000000..f2e1c4a --- /dev/null +++ b/base/md5.cc @@ -0,0 +1,279 @@ +// The original file was copied from sqlite, and was in the public domain. +// Modifications Copyright 2006 Google Inc. All Rights Reserved + +/* + * 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 <string> + +#include "base/md5.h" + +#include "base/basictypes.h" + +struct Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; + +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse (unsigned char *buf, unsigned longs){ + uint32 t; + do { + t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(uint32 *)buf = t; + buf += 4; + } while (--longs); +} +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static 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; +} + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(MD5Context *pCtx){ + struct Context *ctx = (struct Context *)pCtx; + 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 *pCtx, const void *inbuf, size_t len){ + struct Context *ctx = (struct Context *)pCtx; + const unsigned char* buf = (const unsigned char*)inbuf; + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += static_cast<uint32>(len >> 29); + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(MD5Digest* digest, MD5Context *pCtx){ + struct Context *ctx = (struct Context *)pCtx; + 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, static_cast<const unsigned char*>(data), length); + MD5Final(digest, &ctx); +} + +std::string MD5String(const std::string& str) { + MD5Digest digest; + MD5Sum(str.data(), str.length(), &digest); + return MD5DigestToBase16(digest); +} diff --git a/base/md5.h b/base/md5.h new file mode 100644 index 0000000..5fc6795 --- /dev/null +++ b/base/md5.h @@ -0,0 +1,82 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_MD5_H__ +#define BASE_MD5_H__ + +#include <string> + +// 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 +typedef struct MD5Digest_struct { + unsigned char a[16]; +} MD5Digest; + +// 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. +void MD5Sum(const void* data, size_t length, MD5Digest* digest); + +// Initializes the given MD5 context structure for subsequent calls to +// MD5Update. +void MD5Init(MD5Context* context); + +// For the given buffer of data, updates the given MD5 context with the sum of +// the data. You can call this any number of times during the computation, +// exept that MD5Init must have been called first. +void MD5Update(MD5Context* context, const void* buf, size_t len); + +// Finalizes the MD5 operation and fills the buffer with the digest. +void MD5Final(MD5Digest* digest, MD5Context* pCtx); + +// Converts a digest into human-readable hexadecimal. +std::string MD5DigestToBase16(const MD5Digest& digest); + +// Returns the MD5 (in hexadecimal) of a string. +std::string MD5String(const std::string& str); + +#endif // BASE_MD5_H__ diff --git a/base/memory_debug.cc b/base/memory_debug.cc new file mode 100644 index 0000000..6acfe83 --- /dev/null +++ b/base/memory_debug.cc @@ -0,0 +1,79 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifdef PURIFY +// this #define is used to prevent people from directly using pure.h +// instead of memory_debug.h +#define PURIFY_PRIVATE_INCLUDE +#include "base/third_party/purify/pure.h" +#endif + +#include "base/memory_debug.h" + +namespace base { + +bool MemoryDebug::memory_in_use_ = false; + +void MemoryDebug::SetMemoryInUseEnabled(bool enabled) { + memory_in_use_ = enabled; +} + +void MemoryDebug::DumpAllMemoryInUse() { +#ifdef PURIFY + if (memory_in_use_) + PurifyAllInuse(); +#endif +} + +void MemoryDebug::DumpNewMemoryInUse() { +#ifdef PURIFY + if (memory_in_use_) + PurifyNewInuse(); +#endif +} + +void MemoryDebug::DumpAllLeaks() { +#ifdef PURIFY + PurifyAllLeaks(); +#endif +} + +void MemoryDebug::DumpNewLeaks() { +#ifdef PURIFY + PurifyNewLeaks(); +#endif +} + +void MemoryDebug::MarkAsInitialized(void* addr, size_t size) { +#ifdef PURIFY + PurifyMarkAsInitialized(addr, size); +#endif +} + +} // namespace base diff --git a/base/memory_debug.h b/base/memory_debug.h new file mode 100644 index 0000000..c1283dc --- /dev/null +++ b/base/memory_debug.h @@ -0,0 +1,66 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Functions used to debug memory usage, leaks, and other memory issues. +// All methods are effectively no-ops unless this program is being run through +// a supported memory tool (currently, only Purify) + +#ifndef BASE_MEMORY_DEBUG_H_ + +namespace base { + +class MemoryDebug { +public: + // Since MIU messages are a lot of data, and we don't always want this data, + // we have a global switch. If disabled, *MemoryInUse are no-ops. + static void SetMemoryInUseEnabled(bool enabled); + + // Dump information about all memory in use. + static void DumpAllMemoryInUse(); + // Dump information about new memory in use since the last + // call to DumpAllMemoryInUse() or DumpNewMemoryInUse(). + static void DumpNewMemoryInUse(); + + // Dump information about all current memory leaks. + static void DumpAllLeaks(); + // Dump information about new memory leaks since the last + // call to DumpAllLeaks() or DumpNewLeaks() + static void DumpNewLeaks(); + + // Mark |size| bytes of memory as initialized, so it doesn't produce any UMRs + // or UMCs. + static void MarkAsInitialized(void* addr, size_t size); + +private: + static bool memory_in_use_; +}; + +} // namespace base + +#endif
\ No newline at end of file diff --git a/base/message_loop.cc b/base/message_loop.cc new file mode 100644 index 0000000..623ec99 --- /dev/null +++ b/base/message_loop.cc @@ -0,0 +1,969 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <algorithm> + +#include "base/message_loop.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/thread_local_storage.h" +#include "base/win_util.h" + +// a TLS index to the message loop for the current thread +// Note that if we start doing complex stuff in other static initializers +// this could cause problems. +/*static*/ TLSSlot MessageLoop::tls_index_ = ThreadLocalStorage::Alloc(); + +//------------------------------------------------------------------------------ + +static const wchar_t kWndClass[] = L"Chrome_MessageLoopWindow"; + +// Windows Message numbers handled by WindowMessageProc. + +// 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 kMsgPumpATask = WM_USER + 1; + +// Message sent by Quit() to cause our main message pump to terminate as soon as +// all pending task and message queues have been emptied. +static const int kMsgQuit = WM_USER + 2; + +// Logical events for Histogram profiling. Run with -message-loop-histogrammer +// to get an accounting of messages and actions taken on each thread. +static const int kTaskRunEvent = WM_USER + 16; // 0x411 +static const int kSleepingApcEvent = WM_USER + 17; // 0x411 +static const int kPollingSignalEvent = WM_USER + 18; // 0x412 +static const int kSleepingSignalEvent = WM_USER + 19; // 0x413 +static const int kTimerEvent = WM_USER + 20; // 0x414 + +// Provide range of message IDs for use in histogramming and debug display. +static const int kLeastNonZeroMessageId = 1; +static const int kMaxMessageId = 1099; +static const int kNumberOfDistinctMessagesDisplayed = 1100; + +//------------------------------------------------------------------------------ + +static LRESULT CALLBACK MessageLoopWndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + switch (message) { + case kMsgQuit: + case kMsgPumpATask: { + UINT_PTR message_loop_id = static_cast<UINT_PTR>(wparam); + MessageLoop* current_message_loop = + reinterpret_cast<MessageLoop*>(message_loop_id); + DCHECK(MessageLoop::current() == current_message_loop); + return current_message_loop->MessageWndProc(hwnd, message, wparam, + lparam); + } + } + return ::DefWindowProc(hwnd, message, wparam, lparam); +} + +#ifndef NDEBUG +// Force exercise of polling model. +#define CHROME_MAXIMUM_WAIT_OBJECTS 8 +#else +#define CHROME_MAXIMUM_WAIT_OBJECTS MAXIMUM_WAIT_OBJECTS +#endif + +//------------------------------------------------------------------------------ +// A strategy of -1 uses the default case. All strategies are selected as +// positive integers. +// static +int MessageLoop::strategy_selector_ = -1; + +// static +void MessageLoop::SetStrategy(int strategy) { + DCHECK(-1 == strategy_selector_); + strategy_selector_ = strategy; +} + +//------------------------------------------------------------------------------ +// 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; +} + +//------------------------------------------------------------------------------ + +MessageLoop::MessageLoop() : message_hwnd_(NULL), + exception_restoration_(false), + nestable_tasks_allowed_(true), + dispatcher_(NULL), + quit_received_(false), + quit_now_(false), + task_pump_message_pending_(false), + run_depth_(0) { + DCHECK(tls_index_) << "static initializer failed"; + DCHECK(!current()) << "should only have one message loop per thread"; + ThreadLocalStorage::Set(tls_index_, this); + InitMessageWnd(); +} + +MessageLoop::~MessageLoop() { + DCHECK(this == current()); + ThreadLocalStorage::Set(tls_index_, NULL); + DCHECK(!dispatcher_); + DCHECK(!quit_received_ && !quit_now_); + // Most tasks that have not been Run() are deleted in the |timer_manager_| + // destructor after we remove our tls index. We delete the tasks in our + // queues here so their destuction is similar to the tasks in the + // |timer_manager_|. + DeletePendingTasks(); + ReloadWorkQueue(); + DeletePendingTasks(); +} + +void MessageLoop::SetThreadName(const std::string& thread_name) { + DCHECK(thread_name_.empty()); + thread_name_ = thread_name; + StartHistogrammer(); +} + +void MessageLoop::AddObserver(Observer *obs) { + DCHECK(this == current()); + observers_.AddObserver(obs); +} + +void MessageLoop::RemoveObserver(Observer *obs) { + DCHECK(this == current()); + observers_.RemoveObserver(obs); +} + +void MessageLoop::Run() { + Run(NULL); +} + +// Runs the loop in two different SEH modes: +// enable_SEH_restoration_ = false : any unhandled exception goes to the last +// one that calls SetUnhandledExceptionFilter(). +// enable_SEH_restoration_ = true : any unhandled exception goes to the filter +// that was existed before the loop was run. +void MessageLoop::Run(Dispatcher* dispatcher) { + if (exception_restoration_) { + LPTOP_LEVEL_EXCEPTION_FILTER current_filter = GetTopSEHFilter(); + __try { + RunInternal(dispatcher); + } __except(SEHFilter(current_filter)) { + } + } else { + RunInternal(dispatcher); + } +} + +//------------------------------------------------------------------------------ +// Methods supporting various strategies for servicing the numerous queues. +// IF this was just a simple PeekMessage() loop (servicing all passible 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). + +void MessageLoop::RunInternal(Dispatcher* dispatcher) { + // Preserve ability to be called recursively. + ScopedStateSave save(this); // State is restored on exit. + dispatcher_ = dispatcher; + StartHistogrammer(); + + DCHECK(this == current()); + // + // Process all pending messages and signaled objects. + // + // Flush these queues before exiting due to a kMsgQuit or else we risk not + // shutting down properly as some operations may depend on further event + // processing. (Note: some tests may use quit_now_ to exit more swiftly, + // and leave messages pending, so don't assert the above fact). + // + + RunTraditional(); + DCHECK(quit_received_ || quit_now_); +} + +typedef bool (MessageLoop::*ProcessingMethod)(); +typedef ProcessingMethod ProcessingMethods[]; + +void MessageLoop::RunTraditional() { + run_depth_++; + for (;;) { + // If we do any work, we may create more messages etc., and more work + // may possibly be waiting in another task group. In addition, each method + // call here typically limits work to 1 (worst case 2) items. As a result, + // when we (for example) ProcessNextWindowsMessage() there is a good chance + // there are still more waiting (same thing for ProcessNextDeferredTask(), + // which responds to only one signaled object.). 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 = false; + more_work_is_plausible |= ProcessNextWindowsMessage(); + if (quit_now_) + break; + + more_work_is_plausible |= ProcessNextDeferredTask(); + more_work_is_plausible |= ProcessNextObject(); + if (more_work_is_plausible) + continue; + + if (quit_received_) + break; + + // Run any timer that is ready to run. It may create messages etc. + if (ProcessSomeTimers()) + continue; + + // We run delayed non nestable tasks only after all nestable tasks have + // run, to preserve FIFO ordering. + more_work_is_plausible = ProcessNextDelayedNonNestableTask(); + if (more_work_is_plausible) + continue; + + // We service APCs in WaitForWork, without returning. + WaitForWork(); // Wait (sleep) until we have work to do again. + } + + run_depth_--; +} + +bool MessageLoop::ProcessNextDelayedNonNestableTask() { + if (run_depth_ != 1) + return false; + + if (delayed_non_nestable_queue_.Empty()) + return false; + + RunTask(delayed_non_nestable_queue_.Pop()); + return true; +} + +//------------------------------------------------------------------------------ +// Wrapper functions for use in above message loop frameworks. + +bool MessageLoop::ProcessNextDeferredTask() { + ReloadWorkQueue(); + return QueueOrRunTask(NULL); +} + +bool MessageLoop::ProcessSomeTimers() { + return timer_manager_.RunSomePendingTimers(); +} + +//------------------------------------------------------------------------------ + +void MessageLoop::Quit() { + EnsureMessageGetsPosted(kMsgQuit); +} + +bool MessageLoop::WatchObject(HANDLE object, Watcher* watcher) { + DCHECK(this == current()); + DCHECK(object); + DCHECK_NE(object, INVALID_HANDLE_VALUE); + + std::vector<HANDLE>::iterator it = find(objects_.begin(), objects_.end(), + object); + if (watcher) { + if (it == objects_.end()) { + static size_t warning_multiple = 1; + if (objects_.size() >= warning_multiple * MAXIMUM_WAIT_OBJECTS / 2) { + LOG(INFO) << "More than " << warning_multiple * MAXIMUM_WAIT_OBJECTS / 2 + << " objects being watched"; + // This DCHECK() is an artificial limitation, meant to warn us if we + // start creating too many objects. It can safely be raised to a higher + // level, and the program is designed to handle much larger values. + // Before raising this limit, make sure that there is a very good reason + // (in your debug testing) to be watching this many objects. + DCHECK(2 <= warning_multiple); + ++warning_multiple; + } + objects_.push_back(object); + watchers_.push_back(watcher); + } else { + watchers_[it - objects_.begin()] = watcher; + } + } else if (it != objects_.end()) { + std::vector<HANDLE>::difference_type index = it - objects_.begin(); + objects_.erase(it); + watchers_.erase(watchers_.begin() + index); + } + return true; +} + +// Possibly called on a background thread! +void MessageLoop::PostDelayedTask(const tracked_objects::Location& from_here, + Task* task, int delay_ms) { + task->SetBirthPlace(from_here); + DCHECK(delay_ms >= 0); + DCHECK(!task->is_owned_by_message_loop()); + task->set_posted_task_delay(delay_ms); + DCHECK(task->is_owned_by_message_loop()); + PostTaskInternal(task); +} + +void MessageLoop::PostTaskInternal(Task* 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. + + // Local stack variables to use IF we need to process after releasing locks. + HWND message_hwnd; + { + AutoLock lock1(incoming_queue_lock_); + bool was_empty = incoming_queue_.Empty(); + incoming_queue_.Push(task); + if (!was_empty) + return; // Someone else should have started the sub-pump. + + // We may have to start the sub-pump. + AutoLock lock2(task_pump_message_lock_); + if (task_pump_message_pending_) + return; // Someone else continued the pumping. + task_pump_message_pending_ = true; // We'll send one. + message_hwnd = message_hwnd_; + } // Release both locks. + // We may have just posted a kMsgQuit, and so this instance may now destroyed! + // Do not invoke non-static methods, or members in any way! + + // PostMessage may fail, as the hwnd may have vanished due to kMsgQuit. + PostMessage(message_hwnd, kMsgPumpATask, reinterpret_cast<UINT_PTR>(this), 0); +} + +void MessageLoop::InitMessageWnd() { + HINSTANCE hinst = GetModuleHandle(NULL); + + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = MessageLoopWndProc; + wc.hInstance = hinst; + wc.lpszClassName = kWndClass; + RegisterClassEx(&wc); + + message_hwnd_ = CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, + hinst, 0); + DCHECK(message_hwnd_); +} + +LRESULT MessageLoop::MessageWndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + DCHECK(hwnd == message_hwnd_); + switch (message) { + case kMsgPumpATask: { + ProcessPumpReplacementMessage(); // Avoid starving paint and timer. + if (!nestable_tasks_allowed_) + return 0; + PumpATaskDuringWndProc(); + return 0; + } + + case kMsgQuit: { + quit_received_ = true; + return 0; + } + } + return ::DefWindowProc(hwnd, message, wparam, lparam); +} + +void MessageLoop::WillProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(Observer, observers_, WillProcessMessage(msg)); +} + +void MessageLoop::DidProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(Observer, observers_, DidProcessMessage(msg)); +} + +void MessageLoop::SetNestableTasksAllowed(bool allowed) { + nestable_tasks_allowed_ = allowed; + if (!nestable_tasks_allowed_) + return; + // Start the native pump if we are not already pumping. + EnsurePumpATaskWasPosted(); +} + +bool MessageLoop::NestableTasksAllowed() const { + return nestable_tasks_allowed_; +} + + +bool MessageLoop::ProcessNextWindowsMessage() { + MSG msg; + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + return ProcessMessageHelper(msg); + } + return false; +} + +bool MessageLoop::ProcessMessageHelper(const MSG& msg) { + HistogramEvent(msg.message); + + if (WM_QUIT == msg.message) { + // Repost the QUIT message so that it will be retrieved by the primary + // GetMessage() loop. + quit_now_ = true; + PostQuitMessage(static_cast<int>(msg.wParam)); + return false; + } + + // While running our main message pump, we discard kMsgPumpATask messages. + if (msg.message == kMsgPumpATask && msg.hwnd == message_hwnd_) + return ProcessPumpReplacementMessage(); + + WillProcessMessage(msg); + + if (dispatcher_) { + if (!dispatcher_->Dispatch(msg)) + quit_now_ = true; + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + DidProcessMessage(msg); + return true; +} + +bool MessageLoop::ProcessPumpReplacementMessage() { + MSG msg; + bool have_message = (0 != PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)); + DCHECK(!have_message || kMsgPumpATask != msg.message + || msg.hwnd != message_hwnd_); + { + // Since we discarded a kMsgPumpATask message, we must update the flag. + AutoLock lock(task_pump_message_lock_); + DCHECK(task_pump_message_pending_); + task_pump_message_pending_ = false; + } + return have_message && ProcessMessageHelper(msg); +} + +// Create a mini-message-pump to force immediate processing of only Windows +// WM_PAINT messages. +void MessageLoop::PumpOutPendingPaintMessages() { + // 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; + bool win2k(true); + if (win_util::GetWinVersion() > win_util::WINVERSION_2000) + win2k = false; + for (peek_count = 0; peek_count < kMaxPeekCount; ++peek_count) { + MSG msg; + if (win2k) { + if (!PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE)) + break; + } else { + if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_QS_PAINT)) + break; + } + ProcessMessageHelper(msg); + if (quit_now_ ) // Handle WM_QUIT. + break; + } + // Histogram what was really being used, to help to adjust kMaxPeekCount. + DHISTOGRAM_COUNTS(L"Loop.PumpOutPendingPaintMessages Peeks", peek_count); +} + +//------------------------------------------------------------------------------ +// If we handle more than the OS limit on the number of objects that can be +// waited for, we'll need to poll (sequencing through subsets of the objects +// that can be passed in a single OS wait call). The following is the polling +// interval used in that (unusual) case. (I don't have a lot of justifcation +// for the specific value, but it needed to be short enough that it would not +// add a lot of latency, and long enough that we wouldn't thrash the CPU for no +// reason... especially considering the silly user probably has a million tabs +// open, etc.) +static const int kMultipleWaitPollingInterval = 20; + +void MessageLoop::WaitForWork() { + bool original_can_run = nestable_tasks_allowed_; + int wait_flags = original_can_run ? MWMO_ALERTABLE | MWMO_INPUTAVAILABLE + : MWMO_INPUTAVAILABLE; + + bool use_polling = false; // Poll if too many objects for one OS Wait call. + for (;;) { + // Do initialization here, in case APC modifies object list. + size_t total_objs = original_can_run ? objects_.size() : 0; + + int delay; + size_t polling_index = 0; // The first unprocessed object index. + do { + size_t objs_len = + (polling_index < total_objs) ? total_objs - polling_index : 0; + if (objs_len >= CHROME_MAXIMUM_WAIT_OBJECTS) { + objs_len = CHROME_MAXIMUM_WAIT_OBJECTS - 1; + use_polling = true; + } + HANDLE* objs = objs_len ? polling_index + &objects_.front() : NULL; + + // Only wait up to the time needed by the timer manager to fire the next + // set of timers. + delay = timer_manager_.GetCurrentDelay(); + if (use_polling && delay > kMultipleWaitPollingInterval) + delay = kMultipleWaitPollingInterval; + if (delay < 0) // Negative value means no timers waiting. + delay = INFINITE; + + DWORD result; + result = MsgWaitForMultipleObjectsEx(static_cast<DWORD>(objs_len), objs, + delay, QS_ALLINPUT, wait_flags); + + if (WAIT_IO_COMPLETION == result) { + HistogramEvent(kSleepingApcEvent); + // We'll loop here when we service an APC. At it currently stands, + // *ONLY* the IO thread uses *any* APCs, so this should have no impact + // on the UI thread. + break; // Break to outer loop, and waitforwork() again. + } + + // Use unsigned type to simplify range detection; + size_t signaled_index = result - WAIT_OBJECT_0; + if (signaled_index < objs_len) { + SignalWatcher(polling_index + signaled_index); + HistogramEvent(kSleepingSignalEvent); + return; // We serviced a signaled object. + } + + if (objs_len == signaled_index) + return; // A WM_* message is available. + + DCHECK_NE(WAIT_FAILED, result) << GetLastError(); + + DCHECK(!objs || result == WAIT_TIMEOUT); + if (!use_polling) + return; + polling_index += objs_len; + } while (polling_index < total_objs); + // For compatibility, we didn't return sooner. This made us do *some* wait + // call(s) before returning. This will probably change in next rev. + if (!delay || !timer_manager_.GetCurrentDelay()) + return; // No work done, but timer is ready to fire. + } +} + +// Note: MsgWaitMultipleObjects() can't take a nil list, and that is why I had +// to use SleepEx() to handle APCs when there were no objects. +bool MessageLoop::ProcessNextObject() { + if (!nestable_tasks_allowed_) + return false; + + size_t total_objs = objects_.size(); + if (!total_objs) { + return false; + } + + size_t polling_index = 0; // The first unprocessed object index. + do { + DCHECK(polling_index < total_objs); + size_t objs_len = total_objs - polling_index; + if (objs_len >= CHROME_MAXIMUM_WAIT_OBJECTS) + objs_len = CHROME_MAXIMUM_WAIT_OBJECTS - 1; + HANDLE* objs = polling_index + &objects_.front(); + + // Identify 1 pending object, or allow an IO APC to be completed. + DWORD result = WaitForMultipleObjectsEx(static_cast<DWORD>(objs_len), objs, + FALSE, // 1 signal is sufficient. + 0, // Wait 0ms. + false); // Not alertable (no APC). + + // Use unsigned type to simplify range detection; + size_t signaled_index = result - WAIT_OBJECT_0; + if (signaled_index < objs_len) { + SignalWatcher(polling_index + signaled_index); + HistogramEvent(kPollingSignalEvent); + return true; // We serviced a signaled object. + } + + // If an handle is invalid, it will be WAIT_FAILED. + DCHECK_EQ(WAIT_TIMEOUT, result) << GetLastError(); + polling_index += objs_len; + } while (polling_index < total_objs); + return false; // We serviced nothing. +} + +bool MessageLoop::SignalWatcher(size_t object_index) { + BeforeTaskRunSetup(); + DCHECK(objects_.size() > object_index); + // On reception of OnObjectSignaled() to a Watcher object, it may call + // WatchObject(). watchers_ and objects_ will be modified. This is + // expected, so don't be afraid if, while tracing a OnObjectSignaled() + // function, the corresponding watchers_[result] is inexistant. + watchers_[object_index]->OnObjectSignaled(objects_[object_index]); + // Signaled objects tend to be removed from the watch list, and then added + // back (appended). As a result, they move to the end of the objects_ array, + // and this should make their service "fair" (no HANDLEs should be starved). + AfterTaskRunRestore(); + return true; +} + +bool MessageLoop::RunTimerTask(Timer* timer) { + HistogramEvent(kTimerEvent); + Task* task = timer->task(); + if (task->is_owned_by_message_loop()) { + // We constructed it through PostTask(). + DCHECK(!timer->repeating()); + timer->set_task(NULL); + delete timer; + return QueueOrRunTask(task); + } else { + // This is an unknown timer task, and we *can't* delay running it, as a + // user might try to cancel it with TimerManager at any moment. + DCHECK(nestable_tasks_allowed_); + RunTask(task); + return true; + } +} + +void MessageLoop::DiscardTimer(Timer* timer) { + Task* task = timer->task(); + if (task->is_owned_by_message_loop()) { + DCHECK(!timer->repeating()); + timer->set_task(NULL); + delete timer; // We constructed it through PostDelayedTask(). + delete task; // We were given ouwnership in PostTask(). + } +} + +bool MessageLoop::QueueOrRunTask(Task* new_task) { + if (!nestable_tasks_allowed_) { + // Task can't be executed right now. Add it to the queue. + if (new_task) + work_queue_.Push(new_task); + return false; + } + + // Queue new_task first so we execute the task in FIFO order. + if (new_task) + work_queue_.Push(new_task); + + // Execute oldest task. + while (!work_queue_.Empty()) { + Task* task = work_queue_.Pop(); + if (task->nestable() || run_depth_ == 1) { + RunTask(task); + // Show that we ran a task (Note: a new one might arrive as a + // consequence!). + return true; + } else { + // We couldn't run the task now because we're in a nested message loop + // and the task isn't nestable. + delayed_non_nestable_queue_.Push(task); + } + } + + // Nothing happened. + return false; +} + +void MessageLoop::RunTask(Task* task) { + BeforeTaskRunSetup(); + HistogramEvent(kTaskRunEvent); + // task may self-delete during Run() if we don't happen to own it. + // ...so check *before* we Run, since we can't check after. + bool we_own_task = task->is_owned_by_message_loop(); + task->Run(); + if (we_own_task) + task->RecycleOrDelete(); // Relinquish control, and probably delete. + AfterTaskRunRestore(); +} + +void MessageLoop::BeforeTaskRunSetup() { + DCHECK(nestable_tasks_allowed_); + // Execute the task and assume the worst: It is probably not reentrant. + nestable_tasks_allowed_ = false; +} + +void MessageLoop::AfterTaskRunRestore() { + nestable_tasks_allowed_ = true; +} + +void MessageLoop::PumpATaskDuringWndProc() { + // TODO(jar): Perchance we should check on signaled objects here?? + // Signals are generally starved during a native message loop. Even if we + // try to service a signaled object now, we wouldn't automatically get here + // (i.e., the native pump would not re-start) when the next object was + // signaled. If we really want to avoid starving signaled objects, we need + // to translate them into Tasks that can be passed in via PostTask. + // If these native message loops (and sub-pumping activities) are short + // lived, then the starvation won't be that long :-/. + + if (!ProcessNextDeferredTask()) + return; // Nothing to do, so lets stop the sub-pump. + + // We ran a task, so make sure we come back and try to run more tasks. + EnsurePumpATaskWasPosted(); +} + +void MessageLoop::EnsurePumpATaskWasPosted() { + { + AutoLock lock(task_pump_message_lock_); + if (task_pump_message_pending_) + return; // Someone else continued the pumping. + task_pump_message_pending_ = true; // We'll send one. + } + EnsureMessageGetsPosted(kMsgPumpATask); +} + +void MessageLoop::EnsureMessageGetsPosted(int message) const { + const int kRetryCount = 30; + const int kSleepDurationWhenFailing = 100; + for (int i = 0; i < kRetryCount; ++i) { + // Posting to our own windows should always succeed. If it doesn't we're in + // big trouble. + if (PostMessage(message_hwnd_, message, + reinterpret_cast<UINT_PTR>(this), 0)) + return; + Sleep(kSleepDurationWhenFailing); + } + LOG(FATAL) << "Crash with last error " << GetLastError(); + int* p = NULL; + *p = 0; // Crash. +} + +void MessageLoop::ReloadWorkQueue() { + // We can improve performance of our loading tasks from incoming_queue_ to + // work_queue_ by wating until the last minute (work_queue_ is empty) to load. + // That reduces the number of locks-per-task significantly when our queues get + // large. The optimization is disabled on threads that make use of the + // priority queue (prioritization requires all our tasks to be in the + // work_queue_ ASAP). + if (!work_queue_.Empty() && !work_queue_.use_priority_queue()) + return; // Wait till we *really* need to lock and load. + + // Acquire all we can from the inter-thread queue with one lock acquisition. + TaskQueue new_task_list; // Null terminated list. + { + AutoLock lock(incoming_queue_lock_); + if (incoming_queue_.Empty()) + return; + std::swap(incoming_queue_, new_task_list); + DCHECK(incoming_queue_.Empty()); + } // Release lock. + + while (!new_task_list.Empty()) { + Task* task = new_task_list.Pop(); + DCHECK(task->is_owned_by_message_loop()); + + if (task->posted_task_delay() > 0) + timer_manager_.StartTimer(task->posted_task_delay(), task, false); + else + work_queue_.Push(task); + } +} + +void MessageLoop::DeletePendingTasks() { + /* Comment this out as it's causing crashes. + while (!work_queue_.Empty()) { + Task* task = work_queue_.Pop(); + if (task->is_owned_by_message_loop()) + delete task; + } + + while (!delayed_non_nestable_queue_.Empty()) { + Task* task = delayed_non_nestable_queue_.Pop(); + if (task->is_owned_by_message_loop()) + delete task; + } + */ +} + +//------------------------------------------------------------------------------ +// Implementation of the work_queue_ as a ProiritizedTaskQueue + +void MessageLoop::PrioritizedTaskQueue::push(Task * task) { + queue_.push(PrioritizedTask(task, --next_sequence_number_)); +} + +bool MessageLoop::PrioritizedTaskQueue::PrioritizedTask::operator < ( + PrioritizedTask const & right) const { + int compare = task_->priority_ - right.task_->priority_; + if (compare) + return compare < 0; + // Don't compare directly, but rather subtract. This handles overflow + // as sequence numbers wrap around. + compare = sequence_number_ - right.sequence_number_; + DCHECK(compare); // Sequence number are unique for a "long time." + // Make sure we don't starve anything with a low priority. + CHECK(INT_MAX/8 > compare); // We don't get close to wrapping. + CHECK(INT_MIN/8 < compare); // We don't get close to wrapping. + return compare < 0; +} + +//------------------------------------------------------------------------------ +// Implementation of a TaskQueue as a null terminated list, with end pointers. + +void MessageLoop::TaskQueue::Push(Task* task) { + if (!first_) + first_ = task; + else + last_->set_next_task(task); + last_ = task; +} + +Task* MessageLoop::TaskQueue::Pop() { + DCHECK((!first_) == !last_); + Task* task = first_; + if (first_) { + first_ = task->next_task(); + if (!first_) + last_ = NULL; + else + task->set_next_task(NULL); + } + return task; +} + +//------------------------------------------------------------------------------ +// Implementation of a Task queue that automatically switches into a priority +// queue if it observes any non-zero priorities on tasks. + +void MessageLoop::OptionallyPrioritizedTaskQueue::Push(Task* task) { + if (use_priority_queue_) { + prioritized_queue_.push(task); + } else { + queue_.Push(task); + if (task->priority()) { + use_priority_queue_ = true; // From now on. + while (!queue_.Empty()) + prioritized_queue_.push(queue_.Pop()); + } + } +} + +Task* MessageLoop::OptionallyPrioritizedTaskQueue::Pop() { + if (!use_priority_queue_) + return queue_.Pop(); + Task* task = prioritized_queue_.front(); + prioritized_queue_.pop(); + return task; +} + +bool MessageLoop::OptionallyPrioritizedTaskQueue::Empty() { + if (use_priority_queue_) + return prioritized_queue_.empty(); + return queue_.Empty(); +} + +//------------------------------------------------------------------------------ +// Method and data for histogramming events and actions taken by each instance +// on each thread. + +// static +bool MessageLoop::enable_histogrammer_ = false; + +// static +void MessageLoop::EnableHistogrammer(bool enable) { + enable_histogrammer_ = enable; +} + +void MessageLoop::StartHistogrammer() { + if (enable_histogrammer_ && !message_histogram_.get() + && StatisticsRecorder::WasStarted()) { + message_histogram_.reset(new LinearHistogram( + ASCIIToWide("MsgLoop:" + thread_name_).c_str(), + kLeastNonZeroMessageId, + kMaxMessageId, + kNumberOfDistinctMessagesDisplayed)); + message_histogram_->SetFlags(message_histogram_->kHexRangePrintingFlag); + message_histogram_->SetRangeDescriptions(event_descriptions_); + } +} + +void MessageLoop::HistogramEvent(int event) { + if (message_histogram_.get()) + message_histogram_->Add(event); +} + +// Add one undocumented windows message to clean up our display. +#ifndef WM_SYSTIMER +#define WM_SYSTIMER 0x118 +#endif + +// 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}, + + +// static +const LinearHistogram::DescriptionPair MessageLoop::event_descriptions_[] = { + // Only provide an extensive list in debug mode. In release mode, we have to + // read the octal values.... but we save about 450 strings, each of length + // 10 from our binary image. +#ifndef NDEBUG + // Prepare to include a list of names provided in a special header file4. +#define A_NAMED_MESSAGE_FROM_WINUSER_H VALUE_TO_NUMBER_AND_NAME +#include "base/windows_message_list.h" +#undef A_NAMED_MESSAGE_FROM_WINUSER_H + // Add an undocumented message that appeared in our list :-/. + VALUE_TO_NUMBER_AND_NAME(WM_SYSTIMER) +#endif // NDEBUG + + // Provide some pretty print capability in our histogram for our internal + // messages. + + // Values we use for WM_USER+n + VALUE_TO_NUMBER_AND_NAME(kMsgPumpATask) + VALUE_TO_NUMBER_AND_NAME(kMsgQuit) + + // A few events we handle (kindred to messages), and used to profile actions. + VALUE_TO_NUMBER_AND_NAME(kTaskRunEvent) + VALUE_TO_NUMBER_AND_NAME(kSleepingApcEvent) + VALUE_TO_NUMBER_AND_NAME(kSleepingSignalEvent) + VALUE_TO_NUMBER_AND_NAME(kPollingSignalEvent) + VALUE_TO_NUMBER_AND_NAME(kTimerEvent) + + {-1, NULL} // The list must be null terminated, per API to histogram. +}; diff --git a/base/message_loop.h b/base/message_loop.h new file mode 100644 index 0000000..6bb7395 --- /dev/null +++ b/base/message_loop.h @@ -0,0 +1,611 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_MESSAGE_LOOP_H__ +#define BASE_MESSAGE_LOOP_H__ + +#include <windows.h> +#include <deque> +#include <queue> +#include <string> +#include <vector> + +#include "base/histogram.h" +#include "base/observer_list.h" +#include "base/id_map.h" +#include "base/task.h" +#include "base/timer.h" +#include "base/thread_local_storage.h" + +// +// A MessageLoop is used to process events for a particular thread. +// There is at most one MessageLoop instance per thread. +// Events include Windows Message Queue messages, Tasks submitted to PostTask +// or managed by TimerManager, APC calls (as time permits), and signals sent to +// a registered set of HANDLES. +// Processing events corresponds (respectively) to dispatching Windows messages, +// running Tasks, yielding time to APCs, and calling Watchers when the +// corresponding HANDLE is signaled. + +// +// NOTE: Unless otherwise specified, a MessageLoop's methods may only be called +// on the thread where the MessageLoop's Run method executes. +// +// WARNING: 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 windows messages which could +// implicitly start an inner task. Inner messages 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: +// bool old_state = MessageLoop::current()->NestableTasksAllowed(); +// MessageLoop::current()->SetNestableTasksAllowed(true); +// HRESULT hr = DoDragDrop(...); // Implicitly runs a modal message loop here. +// MessageLoop::current()->SetNestableTasksAllowed(old_state); +// // Process hr (the result returned by DoDragDrop(). +// +// Please be **SURE** your task is reentrant and all global variables are stable +// and accessible before calling SetNestableTasksAllowed(true). +// + +// Message loop has several distinct functions. It provides message pumps, +// responds to windows message dispatches, manipulates queues of Tasks. +// The most central operation is the implementation of message pumps, along with +// several subtleties. + +// MessageLoop currently implements several different message pumps. A message +// pump is (traditionally) something that reads from an incoming queue, and then +// dispatches the work. +// +// The first message pump, RunTraditional(), is among other things a +// traditional Windows Message pump. It contains a nearly infinite loop that +// peeks out messages, and then dispatches them. +// Intermixed with those peeks are checks on a queue of Tasks, checks for +// signaled objects, and checks to see if TimerManager has tasks to run. +// When there are no events to be serviced, this pump goes into a wait state. +// For 99.99% of all events, this first message pump handles all processing. +// +// 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,kMsgPumpATask, is repeatedly injected into the Windows +// Message queue. Each time the kMsgPumpATask 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 kMsgPumpATask 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 kMsgPumpATask message is EVER pending in the Winow'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 +// kMsgPumpATask messages. As a result, care is taken to do some peeking in +// between the posting of each kMsgPumpATask message (i.e., after kMsgPumpATask +// is peeked, and before a replacement kMsgPumpATask 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 the RunTraditional() +// loop. + +//------------------------------------------------------------------------------ +// Define a macro to record where (in the sourec code) each Task is posted from. +#define FROM_HERE tracked_objects::Location(__FUNCTION__, __FILE__, __LINE__) + +//------------------------------------------------------------------------------ +class MessageLoop { + public: + + // Select a non-default strategy for serving pending requests, that is to be + // used by all MessageLoop instances. This is called only once before + // constructing any instances. + static void SetStrategy(int strategy); + static void EnableHistogrammer(bool enable_histogrammer); + + // Used with WatchObject to asynchronously monitor the signaled state of a + // HANDLE object. + class Watcher { + public: + virtual ~Watcher() {} + // Called from MessageLoop::Run when a signalled object is detected. + virtual void OnObjectSignaled(HANDLE object) = 0; + }; + + // Dispatcher is used during a nested invocation of Run to dispatch events. + // If Run is invoked with a non-NULL Dispatcher, 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 to dispatch, or not, the event. + // + // The nested loop is exited by either posting a quit, or returning false + // from Dispatch. + class Dispatcher { + public: + // Define a macro for use in the PostTask() or PostDelayedTask() + // invocations. The definition varies depending upon mode (DEBUG, etc.), + // but for now we'll just define it as an int. In other modes it may + // encapsulate the file and line number of the source code where it is + // expanded. + + virtual ~Dispatcher() {} + // Dispatches the event. If true is returned processing continues as + // normal. If false is returned, the nested loop exits immediately. + virtual bool Dispatch(const MSG& msg) = 0; + }; + + // Have the current thread's message loop watch for a signaled object. + // Pass a null watcher to stop watching the object. + bool WatchObject(HANDLE, Watcher*); + + // An Observer is an object that receives global notifications from the + // MessageLoop. + // + // NOTE: An Observer implementation should be extremely fast! + // + class Observer { + public: + virtual ~Observer() {} + + // This method is called before processing a message. + // The message may be undefined in which case msg.message is 0 + virtual void WillProcessMessage(const MSG& msg) = 0; + + // This method is called when control returns from processing a UI message. + // The message may be undefined in which case msg.message is 0 + virtual void DidProcessMessage(const MSG& msg) = 0; + }; + + // Add an Observer, which will start receiving notifications immediately. + void AddObserver(Observer* observer); + + // Remove an Observer. It is safe to call this method while an Observer is + // receiving a notification callback. + void RemoveObserver(Observer* observer); + + // 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 event processing. With the + // PostDelayedTask variant, tasks are called after at least approximately + // 'delay_ms' have elapsed. + // + // The MessageLoop takes ownership of the Task, and deletes it after it + // has been Run(). + // + // NOTE: This method 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, Task* task) { + PostDelayedTask(from_here, task, 0); + } + + void PostDelayedTask(const tracked_objects::Location& from_here, Task* task, + int delay_ms); + + // A variant on PostTask that deletes the given object. This is useful + // if the object needs to live until the next run of the MessageLoop (for + // example, deleting a RenderProcessHost from within an IPC callback is not + // good). + // + // NOTE: This method may be called on any thread. The object will be deleted + // on the thread that executes MessageLoop::Run(). If this is not the same + // as the thread that calls PostDelayedTask(FROM_HERE, ), then T MUST inherit + // from RefCountedThreadSafe<T>! + template <class T> + void DeleteSoon(const tracked_objects::Location& from_here, T* object) { + PostTask(from_here, new DeleteTask<T>(object)); + } + + // A variant on PostTask that releases the given reference counted object + // (by calling its Release method). This is useful if the object needs to + // live until the next run of the MessageLoop, or if the object needs to be + // released on a particular thread. + // + // NOTE: This method may be called on any thread. The object will be + // released (and thus possibly deleted) on the thread that executes + // MessageLoop::Run(). If this is not the same as the thread that calls + // PostDelayedTask(FROM_HERE, ), then T MUST inherit from + // RefCountedThreadSafe<T>! + template <class T> + void ReleaseSoon(const tracked_objects::Location& from_here, T* object) { + PostTask(from_here, new ReleaseTask<T>(object)); + } + + // Run the message loop + void Run(); + + // See description of Dispatcher for how Run uses Dispatcher. + void Run(Dispatcher* dispatcher); + + // Signals the Run method to return after it is done processing all pending + // messages. This method may be called from any thread, but no effort is + // made to support concurrent calls to this method from multiple threads. + // + // For example, the first call to Quit may lead to the MessageLoop being + // deleted once its Run method returns, so a second call from another thread + // could be problematic. + void Quit(); + + // Invokes Quit on the current MessageLoop when run. Useful to schedule an + // arbitrary MessageLoop to Quit. + class QuitTask : public Task { + public: + virtual void Run() { + MessageLoop::current()->Quit(); + } + }; + + // Wnd Proc for message_hwnd_. + LRESULT MessageWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + // Normally, it is not necessary to instantiate a MessageLoop. Instead, it + // is typical to make use of the current thread's MessageLoop instance. + MessageLoop(); + ~MessageLoop(); + + // Optional call to connect the thread name with this loop. + void SetThreadName(const std::string& thread_name); + std::string thread_name() const { return thread_name_; } + + // Returns the MessageLoop object for the current thread, or null if none. + static MessageLoop* current() { + return static_cast<MessageLoop*>(ThreadLocalStorage::Get(tls_index_)); + } + + // Returns the TimerManager object for the current thread. + TimerManager* timer_manager() { return &timer_manager_; } + + // Give a chance to code processing additional messages to notify the + // message loop delegates that another message has been processed. + void WillProcessMessage(const MSG& msg); + void DidProcessMessage(const MSG& msg); + + // 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. + // + // 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 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; + } + + // Public entry point for TimerManager to request the Run() of a task. If we + // created the task during an PostTask(FROM_HERE, ), then we will also perform + // destructions, and we'll have the option of queueing the task. If we didn't + // create the timer, then we will Run it immediately. + bool RunTimerTask(Timer* timer); + + // Since some Timer's are owned by MessageLoop, the TimerManager (when it is + // being destructed) passses us the timers to discard (without doing a Run()). + void DiscardTimer(Timer* timer); + + // 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: + struct ScopedStateSave { + explicit ScopedStateSave(MessageLoop* loop) + : loop_(loop), + dispatcher_(loop->dispatcher_), + quit_now_(loop->quit_now_), + quit_received_(loop->quit_received_) { + loop->quit_now_ = loop->quit_received_ = false; + } + + ~ScopedStateSave() { + loop_->quit_received_ = quit_received_; + loop_->quit_now_ = quit_now_; + loop_->dispatcher_ = dispatcher_; + } + + private: + MessageLoop* loop_; + Dispatcher* dispatcher_; + bool quit_now_; + bool quit_received_; + }; // struct ScopedStateSave + + // A prioritized queue with interface that mostly matches std::queue<>. + // For debugging/performance testing, you can swap in std::queue<Task*>. + class PrioritizedTaskQueue { + public: + PrioritizedTaskQueue() : next_sequence_number_(0) {} + ~PrioritizedTaskQueue() {} + void pop() { queue_.pop(); } + bool empty() { return queue_.empty(); } + size_t size() { return queue_.size(); } + Task* front() { return queue_.top().task(); } + void push(Task * task); + + private: + class PrioritizedTask { + public: + PrioritizedTask(Task* task, int sequence_number) + : task_(task), + sequence_number_(sequence_number), + priority_(task->priority()) {} + Task* task() { return task_; } + bool operator < (PrioritizedTask const & right) const ; + + private: + Task* task_; + // Number to ensure (default) FIFO ordering in a PriorityQueue. + int sequence_number_; + // Priority of task when pushed. + int priority_; + }; // class PrioritizedTask + + std::priority_queue<PrioritizedTask> queue_; + // Default sequence number used when push'ing (monotonically decreasing). + int next_sequence_number_; + DISALLOW_EVIL_CONSTRUCTORS(PrioritizedTaskQueue); + }; + + // Implementation of a TaskQueue as a null terminated list, with end pointers. + class TaskQueue { + public: + TaskQueue() : first_(NULL), last_(NULL) {} + void Push(Task* task); + Task* Pop(); // Extract the next Task from the queue, and return it. + bool Empty() const { return !first_; } + friend void std::swap<TaskQueue>(TaskQueue&, TaskQueue&); + private: + Task* first_; + Task* last_; + }; + + // Implementation of a Task queue that automatically switches into a priority + // queue if it observes any non-zero priorities in tasks. + class OptionallyPrioritizedTaskQueue { + public: + OptionallyPrioritizedTaskQueue() : use_priority_queue_(false) {} + void Push(Task* task); + Task* Pop(); // Extract next Task from queue, and return it. + bool Empty(); + bool use_priority_queue() const { return use_priority_queue_; } + + private: + bool use_priority_queue_; + PrioritizedTaskQueue prioritized_queue_; + TaskQueue queue_; + DISALLOW_EVIL_CONSTRUCTORS(OptionallyPrioritizedTaskQueue); + }; + + void InitMessageWnd(); + + // The actual message loop implementation. Called by all flavors of Run(). + // It will run the message loop in a SEH try block or not depending on the + // set_SEH_restoration() flag. + void RunInternal(Dispatcher* dispatcher); + + //---------------------------------------------------------------------------- + // A list of alternate message loop priority systems. The strategy_selector_ + // determines which one to actually use. + void RunTraditional(); + + //---------------------------------------------------------------------------- + // A list of method wrappers with identical calling signatures (no arguments) + // for use in the main message loop. Method pointers to these methods may be + // called round-robin from the main message loop, on any desired schedule. + + bool ProcessNextDeferredTask(); + bool ProcessNextDelayedNonNestableTask(); + bool ProcessNextObject(); + bool ProcessSomeTimers(); + + //---------------------------------------------------------------------------- + // Process some pending messages. + // Returns true if a message was processed. + bool ProcessNextWindowsMessage(); + + // Wait until either an object is signaled, a message is available, a timer + // needs attention, or our incoming_queue_ has gotten a task. + // Handle (without returning) any APCs (only IO thread currently has APCs.) + void WaitForWork(); + + // Helper function for processing window messages. This includes handling + // WM_QUIT, message translation and dispatch, etc. + // + // If dispatcher_ is non-NULL this method does NOT dispatch the event, instead + // it invokes Dispatch on the dispatcher_. + bool ProcessMessageHelper(const MSG& msg); + + // When we encounter a kMsgPumpATask, the following helper can be called to + // peek and process a replacement message, such as a WM_PAINT or WM_TIMER. + // The goal is to make the kMsgPumpATask 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 kMsgPumpATask to + // be pending, then releases the lock (allowing a replacement kMsgPumpATask to + // possibly be posted), and finally dispatches that peeked replacement. + // Note that the re-post of kMsgPumpATask may be asynchronous to this thread!! + bool ProcessPumpReplacementMessage(); + + // Signals a watcher if a wait falls within the range of objects we're + // waiting on. object_index is the offset in objects_ that was signaled. + // Returns true if an object was signaled. + bool SignalWatcher(size_t object_index); + + // Run a work_queue_ task or new_task, and delete it (if it was processed by + // PostTask). If there are queued tasks, the oldest one is executed and + // new_task is queued. new_task is optional and can be NULL. In this NULL + // case, the method will run one pending task (if any exist). Returns true if + // it executes a task. + // Queued tasks accumulate only when there is a nonreentrant task currently + // processing, in which case the new_task is appended to the list + // work_queue_. Such re-entrancy generally happens when an unrequested + // message pump (typical of a native dialog) is executing in the context of a + // task. + bool QueueOrRunTask(Task* new_task); + + // Runs the specified task and deletes it. + void RunTask(Task* task); + + // Make state adjustments just before and after running tasks so that we can + // continue to work if a native message loop is employed during a task. + void BeforeTaskRunSetup(); + void AfterTaskRunRestore(); + + // When processing messages in our MessageWndProc(), we are sometimes called + // by a native message pump (i.e., We are not called out of our Run() pump). + // In those cases, we need to process tasks during the Windows Message + // callback. This method processes a task, and also posts a new kMsgPumpATask + // messages to the Windows Msg Queue so that we are called back later (to + // process additional tasks). + void PumpATaskDuringWndProc(); + + // Load tasks from the incoming_queue_ into work_queue_ if the latter is + // empty. The former requires a lock to access, while the latter is directly + // accessible on this thread. + void ReloadWorkQueue(); + + // Delete tasks that haven't run yet without running them. Used in the + // destructor to make sure all the task's destructors get called. + void DeletePendingTasks(); + + // Make sure a kPumpATask message is in flight, which starts/continues the + // sub-pump. + void EnsurePumpATaskWasPosted(); + + // Do a PostMessage(), and crash if we can't eventually do the post. + void EnsureMessageGetsPosted(int message) const; + + // Post a task to our incomming queue. + void MessageLoop::PostTaskInternal(Task* task); + + // 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 occurence 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); + + static TLSSlot tls_index_; + static int strategy_selector_; + static const LinearHistogram::DescriptionPair event_descriptions_[]; + static bool enable_histogrammer_; + + TimerManager timer_manager_; + + // 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. + // As an optimization, when we don't need to use the prioritization of + // work_queue_, we use a null terminated list (TaskQueue) as our + // implementation of the queue. This saves on memory (list uses pointers + // internal to Task) and probably runs faster than the priority queue when + // there was no real prioritization. + OptionallyPrioritizedTaskQueue work_queue_; + + // A vector of objects (and corresponding watchers) that are routinely + // serviced by this message loop's pump. + std::vector<HANDLE> objects_; + std::vector<Watcher*> watchers_; + + ObserverList<Observer> observers_; + HWND message_hwnd_; + IDMap<Task> timed_tasks_; + // A recursion block that prevents accidentally running additonal tasks when + // insider a (accidentally induced?) nested message pump. + bool nestable_tasks_allowed_; + + bool exception_restoration_; + + Dispatcher* dispatcher_; + bool quit_received_; + bool quit_now_; + + std::string thread_name_; + // A profiling histogram showing the counts of various messages and events. + scoped_ptr<LinearHistogram> message_histogram_; + + // A null terminated list which creates an incoming_queue of tasks that are + // aquired under a mutex for processing on this instance's thread. These tasks + // have not yet been sorted out into items for our work_queue_ vs items that + // will be handled by the TimerManager. + TaskQueue incoming_queue_; + // Protect access to incoming_queue_. + Lock incoming_queue_lock_; + + // A null terminated list of non-nestable tasks that we had to delay 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 delayed_non_nestable_queue_; + + // Indicate if there is a kMsgPumpATask 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. + bool task_pump_message_pending_; + // Protect access to task_pump_message_pending_. + Lock task_pump_message_lock_; + + // Used to count how many Run() invocations are on the stack. + int run_depth_; + + DISALLOW_EVIL_CONSTRUCTORS(MessageLoop); +}; + +#endif // BASE_MESSAGE_LOOP_H__ diff --git a/base/message_loop_unittest.cc b/base/message_loop_unittest.cc new file mode 100644 index 0000000..374a618 --- /dev/null +++ b/base/message_loop_unittest.cc @@ -0,0 +1,827 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/scoped_handle.h" +#include "base/thread.h" +#include "base/ref_counted.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MessageLoopTest : public testing::Test { + public: + virtual void SetUp() { + enable_recursive_task_ = MessageLoop::current()->NestableTasksAllowed(); + } + virtual void TearDown() { + MessageLoop::current()->SetNestableTasksAllowed(enable_recursive_task_); + } + private: + bool enable_recursive_task_; +}; + +class Foo : public base::RefCounted<Foo> { + public: + Foo() : test_count_(0) { + } + + void Test0() { + ++test_count_; + } + + void Test1ConstRef(const std::string& a) { + ++test_count_; + result_.append(a); + } + + void Test1Ptr(std::string* a) { + ++test_count_; + result_.append(*a); + } + + void Test1Int(int a) { + test_count_ += a; + } + + void Test2Ptr(std::string* a, std::string* b) { + ++test_count_; + result_.append(*a); + result_.append(*b); + } + + void Test2Mixed(const std::string& a, std::string* b) { + ++test_count_; + result_.append(a); + result_.append(*b); + } + + int test_count() const { return test_count_; } + const std::string& result() const { return result_; } + + private: + int test_count_; + std::string result_; +}; + +class QuitMsgLoop : public base::RefCounted<QuitMsgLoop> { + public: + void QuitNow() { + MessageLoop::current()->Quit(); + } +}; + +} // namespace + +TEST(MessageLoopTest, PostTask) { + // Add tests to message loop + scoped_refptr<Foo> foo = new Foo(); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test0)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1ConstRef, a)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Ptr, &b)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Int, 100)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Ptr, &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Mixed, a, &d)); + + // After all tests, post a message that will shut down the message loop + scoped_refptr<QuitMsgLoop> quit = new QuitMsgLoop(); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + quit.get(), &QuitMsgLoop::QuitNow)); + + // Now kick things off + MessageLoop::current()->Run(); + + EXPECT_EQ(foo->test_count(), 105); + EXPECT_EQ(foo->result(), "abacad"); +} + +TEST(MessageLoopTest, InvokeLater_SEH) { + // Add tests to message loop + scoped_refptr<Foo> foo = new Foo(); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test0)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1ConstRef, a)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Ptr, &b)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Int, 100)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Ptr, &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Mixed, a, &d)); + + // After all tests, post a message that will shut down the message loop + scoped_refptr<QuitMsgLoop> quit = new QuitMsgLoop(); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + quit.get(), &QuitMsgLoop::QuitNow)); + + // 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"); +} + +namespace { + +class NestingTest : public Task { + public: + explicit NestingTest(int* depth) : depth_(depth) { + } + void Run() { + if (*depth_ > 0) { + *depth_ -= 1; + MessageLoop::current()->PostTask(FROM_HERE, new NestingTest(depth_)); + + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + } + MessageLoop::current()->Quit(); + } + private: + int* depth_; +}; + +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 CrasherTask : public Task { + public: + // Ctor. If trash_SEH_handler is true, the task will override the unhandled + // exception handler with one sure to crash this test. + explicit CrasherTask(bool trash_SEH_handler) + : trash_SEH_handler_(trash_SEH_handler) { + } + void Run() { + Sleep(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. + __asm { + mov eax, dword ptr [CrasherTask::bad_array_] + mov byte ptr [eax], 66 + } + MessageLoop::current()->Quit(); + } + // Points the bad array to a valid memory location. + static void FixError() { + bad_array_ = &valid_store_; + } + + private: + bool trash_SEH_handler_; + static volatile char* bad_array_; + static char valid_store_; +}; + +volatile char* CrasherTask::bad_array_ = 0; +char CrasherTask::valid_store_ = 0; + +// This SEH filter fixes the problem and retries execution. Fixing requires +// that the last instruction: mov eax, [CrasherTask::bad_array_] to be retried +// so we move the instruction pointer 5 bytes back. +LONG WINAPI HandleCrasherTaskException(EXCEPTION_POINTERS *ex_info) { + if (ex_info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) + return EXCEPTION_EXECUTE_HANDLER; + + CrasherTask::FixError(); + ex_info->ContextRecord->Eip -= 5; + return EXCEPTION_CONTINUE_EXECUTION; +} + +} // namespace + + +TEST(MessageLoopTest, Crasher) { + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherTaskException); + + MessageLoop::current()->PostTask(FROM_HERE, new CrasherTask(false)); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::SetUnhandledExceptionFilter(old_SEH_filter); +} + + +TEST(MessageLoopTest, CrasherNasty) { + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherTaskException); + + MessageLoop::current()->PostTask(FROM_HERE, new CrasherTask(true)); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::SetUnhandledExceptionFilter(old_SEH_filter); +} + +TEST(MessageLoopTest, Nesting) { + int depth = 100; + MessageLoop::current()->PostTask(FROM_HERE, new NestingTest(&depth)); + MessageLoop::current()->Run(); + EXPECT_EQ(depth, 0); +} + +namespace { + +const wchar_t* const kMessageBoxTitle = L"MessageLoop Unit Test"; + +enum TaskType { + MESSAGEBOX, + ENDDIALOG, + RECURSIVE, + TIMEDMESSAGELOOP, + QUITMESSAGELOOP, + ORDERERD, + PUMPS, +}; + +// 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; + } +}; + +typedef std::vector<TaskItem> TaskList; + +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 ORDERERD: os << "ORDERERD"; break; + case PUMPS: os << "PUMPS"; 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"; +} + +// Saves the order the tasks ran. +class OrderedTasks : public Task { + public: + OrderedTasks(TaskList* order, int cookie) + : order_(order), + type_(ORDERERD), + cookie_(cookie) { + } + OrderedTasks(TaskList* order, TaskType type, int cookie) + : order_(order), + type_(type), + cookie_(cookie) { + } + + void RunStart() { + TaskItem item(type_, cookie_, true); + DLOG(INFO) << item; + order_->push_back(item); + } + void RunEnd() { + TaskItem item(type_, cookie_, false); + DLOG(INFO) << item; + order_->push_back(item); + } + + virtual void Run() { + RunStart(); + RunEnd(); + } + + protected: + TaskList* order() const { + return order_; + } + + int cookie() const { + return cookie_; + } + + private: + TaskList* order_; + TaskType type_; + int cookie_; +}; + +// MessageLoop implicitly start a "modal message loop". Modal dialog boxes, +// common controls (like OpenFile) and StartDoc printing function can cause +// implicit message loops. +class MessageBoxTask : public OrderedTasks { + public: + MessageBoxTask(TaskList* order, int cookie, bool is_reentrant) + : OrderedTasks(order, MESSAGEBOX, cookie), + is_reentrant_(is_reentrant) { + } + + virtual void Run() { + RunStart(); + if (is_reentrant_) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK); + RunEnd(); + } + + private: + bool is_reentrant_; +}; + +// Will end the MessageBox. +class EndDialogTask : public OrderedTasks { + public: + EndDialogTask(TaskList* order, int cookie) + : OrderedTasks(order, ENDDIALOG, cookie) { + } + + virtual void Run() { + RunStart(); + 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. + RunEnd(); + } + } +}; + +class RecursiveTask : public OrderedTasks { + public: + RecursiveTask(int depth, TaskList* order, int cookie, bool is_reentrant) + : OrderedTasks(order, RECURSIVE, cookie), + depth_(depth), + is_reentrant_(is_reentrant) { + } + + virtual void Run() { + RunStart(); + if (depth_ > 0) { + if (is_reentrant_) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(depth_ - 1, order(), cookie(), is_reentrant_)); + } + RunEnd(); + } + + private: + int depth_; + bool is_reentrant_; +}; + +class QuitTask : public OrderedTasks { + public: + QuitTask(TaskList* order, int cookie) + : OrderedTasks(order, QUITMESSAGELOOP, cookie) { + } + + virtual void Run() { + RunStart(); + MessageLoop::current()->Quit(); + RunEnd(); + } +}; + +class Recursive2Tasks : public Task { + public: + Recursive2Tasks(MessageLoop* target, + HANDLE event, + bool expect_window, + TaskList* order, + bool is_reentrant) + : target_(target), + event_(event), + expect_window_(expect_window), + order_(order), + is_reentrant_(is_reentrant) { + } + + virtual void Run() { + target_->PostTask(FROM_HERE, + new RecursiveTask(2, order_, 1, is_reentrant_)); + target_->PostTask(FROM_HERE, + new MessageBoxTask(order_, 2, is_reentrant_)); + target_->PostTask(FROM_HERE, + new RecursiveTask(2, order_, 3, 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, new EndDialogTask(order_, 4)); + target_->PostTask(FROM_HERE, new QuitTask(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_TRUE(0 == SendMessage(button, WM_LBUTTONDOWN, 0, 0)); + EXPECT_TRUE(0 == SendMessage(button, WM_LBUTTONUP, 0, 0)); + break; + } + } + break; + } + } + } + + private: + MessageLoop* target_; + HANDLE event_; + TaskList* order_; + bool expect_window_; + bool is_reentrant_; +}; + +} // namespace + +TEST(MessageLoop, RecursiveDenial1) { + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + TaskList order; + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 1, false)); + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 2, false)); + MessageLoop::current()->PostTask(FROM_HERE, new QuitTask(&order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 14); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order[ 6], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 7], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 8], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 9], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 2, false)); +} + + +TEST(MessageLoop, RecursiveSupport1) { + TaskList order; + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 1, true)); + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 2, true)); + MessageLoop::current()->PostTask(FROM_HERE, + new QuitTask(&order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 14); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order[ 6], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 7], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 8], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 9], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 2, false)); +} + +// A side effect of this test is the generation a beep. Sorry. +TEST(MessageLoop, RecursiveDenial2) { + Thread worker("RecursiveDenial2_worker"); + ASSERT_EQ(true, worker.Start()); + TaskList order; + ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + new Recursive2Tasks(MessageLoop::current(), + event, + true, + &order, + false)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.size(), 17); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(MESSAGEBOX, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(MESSAGEBOX, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(RECURSIVE, 3, false)); + // When EndDialogTask is processed, the window is already dismissed, hence no + // "end" entry. + EXPECT_EQ(order[ 6], TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order[ 7], TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order[ 8], TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order[ 9], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[14], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[15], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[16], TaskItem(RECURSIVE, 3, false)); +} + +// A side effect of this test is the generation a beep. Sorry. +TEST(MessageLoop, RecursiveSupport2) { + Thread worker("RecursiveSupport2_worker"); + ASSERT_EQ(true, worker.Start()); + TaskList order; + ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + new Recursive2Tasks(MessageLoop::current(), + event, + false, + &order, + true)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.size(), 18); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(MESSAGEBOX, 2, true)); + // Note that this executes in the MessageBox modal loop. + EXPECT_EQ(order[ 3], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[ 4], TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order[ 5], TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order[ 6], TaskItem(ENDDIALOG, 4, false)); + EXPECT_EQ(order[ 7], TaskItem(MESSAGEBOX, 2, false)); + /* The order can subtly change here. The reason is that when RecursiveTask(1) + is called in the main thread, if it is faster than getting to the + PostTask(FROM_HERE, QuitTask) execution, the order of task execution can + change. We don't care anyway that the order isn't correct. + EXPECT_EQ(order[ 8], TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order[ 9], TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 1, false)); + */ + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order[14], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[15], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[16], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[17], TaskItem(RECURSIVE, 3, false)); +} + +class TaskThatPumps : public OrderedTasks { + public: + TaskThatPumps(TaskList* order, int cookie) + : OrderedTasks(order, PUMPS, cookie) { + } + + virtual void Run() { + RunStart(); + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->Quit(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + MessageLoop::current()->SetNestableTasksAllowed(old_state); + RunEnd(); + } + + private: +}; + + +// Tests that non nestable tasks run in FIFO if there are no nested loops. +TEST(MessageLoop, NonNestableWithNoNesting) { + TaskList order; + + Task* task = new OrderedTasks(&order, 1); + task->set_nestable(false); + MessageLoop::current()->PostTask(FROM_HERE, task); + MessageLoop::current()->PostTask(FROM_HERE, new OrderedTasks(&order, 2)); + MessageLoop::current()->PostTask(FROM_HERE, new QuitTask(&order, 3)); + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 6); + EXPECT_EQ(order[ 0], TaskItem(ORDERERD, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(ORDERERD, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(ORDERERD, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(ORDERERD, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(QUITMESSAGELOOP, 3, false)); +} + +// Tests that non nestable tasks don't run when there's code in the call stack. +TEST(MessageLoop, NonNestableInNestedLoop) { + TaskList order; + + MessageLoop::current()->PostTask(FROM_HERE, + new TaskThatPumps(&order, 1)); + Task* task = new OrderedTasks(&order, 2); + task->set_nestable(false); + MessageLoop::current()->PostTask(FROM_HERE, task); + MessageLoop::current()->PostTask(FROM_HERE, new OrderedTasks(&order, 3)); + MessageLoop::current()->PostTask(FROM_HERE, new QuitTask(&order, 4)); + Task* non_nestable_quit = new QuitTask(&order, 5); + non_nestable_quit->set_nestable(false); + MessageLoop::current()->PostTask(FROM_HERE, non_nestable_quit); + + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 10); + EXPECT_EQ(order[ 0], TaskItem(PUMPS, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(ORDERERD, 3, true)); + EXPECT_EQ(order[ 2], TaskItem(ORDERERD, 3, false)); + EXPECT_EQ(order[ 3], TaskItem(QUITMESSAGELOOP, 4, true)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 4, false)); + EXPECT_EQ(order[ 5], TaskItem(PUMPS, 1, false)); + EXPECT_EQ(order[ 6], TaskItem(ORDERERD, 2, true)); + EXPECT_EQ(order[ 7], TaskItem(ORDERERD, 2, false)); + EXPECT_EQ(order[ 8], TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order[ 9], TaskItem(QUITMESSAGELOOP, 5, false)); +} + + +namespace { + +class AutoresetWatcher : public MessageLoop::Watcher { + public: + AutoresetWatcher(HANDLE signal, MessageLoop* message_loop) + : signal_(signal), message_loop_(message_loop) {} + virtual void OnObjectSignaled(HANDLE object); + private: + HANDLE signal_; + MessageLoop* message_loop_; +}; + +void AutoresetWatcher::OnObjectSignaled(HANDLE object) { + message_loop_->WatchObject(object, NULL); + ASSERT_TRUE(SetEvent(signal_)); +} + +class AutoresetTask : public Task { + public: + AutoresetTask(HANDLE object, MessageLoop::Watcher* watcher) + : object_(object), watcher_(watcher) {} + virtual void Run() { + MessageLoop::current()->WatchObject(object_, watcher_); + } + + private: + HANDLE object_; + MessageLoop::Watcher* watcher_; +}; + +} // namespace + +TEST(MessageLoop, AutoresetEvents) { + SECURITY_ATTRIBUTES attributes; + attributes.nLength = sizeof(attributes); + attributes.bInheritHandle = false; + attributes.lpSecurityDescriptor = NULL; + + // Init an autoreset and a manual reset events. + HANDLE autoreset = CreateEvent(&attributes, FALSE, FALSE, NULL); + HANDLE callback_called = CreateEvent(&attributes, TRUE, FALSE, NULL); + ASSERT_TRUE(NULL != autoreset); + ASSERT_TRUE(NULL != callback_called); + + Thread thread("Autoreset test"); + ASSERT_TRUE(thread.Start()); + + MessageLoop* message_loop = thread.message_loop(); + ASSERT_TRUE(NULL != message_loop); + + AutoresetWatcher watcher(callback_called, message_loop); + AutoresetTask* task = new AutoresetTask(autoreset, &watcher); + message_loop->PostTask(FROM_HERE, task); + Sleep(100); // Make sure the thread runs and sleeps for lack of work. + + ASSERT_TRUE(SetEvent(autoreset)); + + DWORD result = WaitForSingleObject(callback_called, 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +namespace { + +class DispatcherImpl : public MessageLoop::Dispatcher { + public: + DispatcherImpl() : dispatch_count_(0) {} + + virtual bool Dispatch(const MSG& msg) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + return (++dispatch_count_ != 2); + } + + int dispatch_count_; +}; + +} // namespace + +TEST(MessageLoop, Dispatcher) { + class MyTask : public Task { + public: + virtual void Run() { + PostMessage(NULL, WM_LBUTTONDOWN, 0, 0); + PostMessage(NULL, WM_LBUTTONUP, 'A', 0); + } + }; + Task* task = new MyTask(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, task, 100); + DispatcherImpl dispatcher; + MessageLoop::current()->Run(&dispatcher); + ASSERT_EQ(2, dispatcher.dispatch_count_); +} diff --git a/base/multiprocess_test.h b/base/multiprocess_test.h new file mode 100644 index 0000000..21127f3 --- /dev/null +++ b/base/multiprocess_test.h @@ -0,0 +1,83 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_MULTIPROCESS_TEST_H__ +#define BASE_MULTIPROCESS_TEST_H__ + +#include "base/command_line.h" +#include "base/process_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Command line switch to invoke a child process rather than +// to run the normal test suite. +static const wchar_t kRunClientProcess[] = L"client"; + +// A MultiProcessTest is a test class which makes it easier to +// write a test which requires code running out of process. +// +// To create a multiprocess test simply follow these steps: +// +// 1) Derive your test from MultiProcessTest. +// 2) Modify your mainline so that if it sees the +// kRuNClientProcess switch, it will deal with it. +// 3) Create a mainline function for the child processes +// 4) Call SpawnChild("foo"), where "foo" is the name of +// the function you wish to run in the child processes. +// That's it! +// +class MultiProcessTest : public testing::Test { + public: + // Prototype function for a client function. Multi-process + // clients must provide a callback with this signature to run. + typedef int (__cdecl *ChildFunctionPtr)(); + + protected: + // Run a child process. + // 'procname' is the name of a function which the child will + // execute. It must be exported from this library in order to + // run. + // + // Example signature: + // extern "C" int __declspec(dllexport) FooBar() { + // // do client work here + // } + // + // Returns the handle to the child, or NULL on failure + HANDLE SpawnChild(const std::wstring& procname) { + std::wstring cl(GetCommandLineW()); + CommandLine::AppendSwitchWithValue(&cl, kRunClientProcess, procname); + // TODO(darin): re-enable this once we have base/debug_util.h + //ProcessDebugFlags(&cl, DebugUtil::UNKNOWN, false); + HANDLE handle = NULL; + process_util::LaunchApp(cl, false, true, &handle); + return handle; + } +}; + +#endif // BASE_MULTIPROCESS_TEST_H__ diff --git a/base/no_windows2000_unittest.h b/base/no_windows2000_unittest.h new file mode 100644 index 0000000..5bef8b8 --- /dev/null +++ b/base/no_windows2000_unittest.h @@ -0,0 +1,46 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_NO_WINDOWS2000_UNITTEST_H__ +#define BASE_NO_WINDOWS2000_UNITTEST_H__ + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/win_util.h" + +// Disable the whole test case when executing on Windows 2000 or lower. +// Note: Parent should be testing::Test or UITest. +template<typename Parent> +class NoWindows2000Test : public Parent { + public: + static bool IsTestCaseDisabled() { + return win_util::GetWinVersion() <= win_util::WINVERSION_2000; + } +}; + +#endif // BASE_NO_WINDOWS2000_UNITTEST_H__op diff --git a/base/non_thread_safe.cc b/base/non_thread_safe.cc new file mode 100644 index 0000000..eefc9a5 --- /dev/null +++ b/base/non_thread_safe.cc @@ -0,0 +1,48 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/non_thread_safe.h" + +#include "base/message_loop.h" + +// These checks are only done in release builds. +#ifndef NDEBUG + +NonThreadSafe::NonThreadSafe() : valid_thread_id_(GetCurrentThreadId()) { +} + +bool NonThreadSafe::CalledOnValidThread() const { + return valid_thread_id_ == GetCurrentThreadId(); +} + +NonThreadSafe::~NonThreadSafe() { + DCHECK(CalledOnValidThread()); +} + +#endif diff --git a/base/non_thread_safe.h b/base/non_thread_safe.h new file mode 100644 index 0000000..e3f4e97 --- /dev/null +++ b/base/non_thread_safe.h @@ -0,0 +1,79 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_NON_THREAD_SAFE_H__ +#define BASE_NON_THREAD_SAFE_H__ + +#include "base/logging.h" + +// A helper class used to help verify that methods of a class are +// called from the same thread. One can inherit from this class and use +// CalledOnValidThread() to verify. +// +// This is intended to be used with classes that appear to be thread safe, but +// aren't. For example, a service or a singleton like the preferences system. +// +// Example: +// class MyClass : public NonThreadSafe { +// public: +// void Foo() { +// DCHECK(CalledOnValidThread()); +// ... (do stuff) ... +// } +// } +// +// In Release mode, CalledOnValidThread will always return true. +// +#ifndef NDEBUG +class NonThreadSafe { + public: + NonThreadSafe(); + ~NonThreadSafe(); + + protected: + bool CalledOnValidThread() const; + + private: + int32 valid_thread_id_; +}; +#else +// Do nothing in release mode. +class NonThreadSafe { + public: + NonThreadSafe() {} + ~NonThreadSafe() {} + + protected: + bool CalledOnValidThread() const { + return true; + } +}; +#endif // NDEBUG + +#endif // BASE_NON_THREAD_SAFE_H__ diff --git a/base/observer_list.h b/base/observer_list.h new file mode 100644 index 0000000..a64808d --- /dev/null +++ b/base/observer_list.h @@ -0,0 +1,170 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_OBSERVER_LIST_H__ +#define BASE_OBSERVER_LIST_H__ + +#include <vector> +#include <algorithm> + +#include "base/basictypes.h" +#include "base/logging.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// OVERVIEW: +// +// A container for a list of observers. Unlike a normal STL vector or list, +// this container can be modified during iteration without invalidating the +// iterator. So, it safely handles the case of an observer removing itself +// or other observers from the list while observers are being notified. +// +// TYPICAL USAGE: +// +// class MyWidget { +// public: +// ... +// +// class Observer { +// public: +// virtual void OnFoo(MyWidget* w) = 0; +// virtual void OnBar(MyWidget* w, int x, int y) = 0; +// }; +// +// void AddObserver(Observer* obs) { +// observer_list_.AddObserver(obs); +// } +// +// void RemoveObserver(Observer* obs) { +// observer_list_.RemoveObserver(obs); +// } +// +// void NotifyFoo() { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnFoo(this)); +// } +// +// void NotifyBar(int x, int y) { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnBar(this, x, y)); +// } +// +// private: +// ObserverList<Observer> observer_list_; +// }; +// +/////////////////////////////////////////////////////////////////////////////// + +template <class ObserverType, bool check_empty = false> +class ObserverList { + public: + ObserverList() : notify_depth_(0) {} + ~ObserverList() { + // When check_empty is true, assert that the list is empty on destruction. + if (check_empty) { + Compact(); + DCHECK_EQ(observers_.size(), 0); + } + } + + // Add an observer to the list. + void AddObserver(ObserverType* obs) { + DCHECK(find(observers_.begin(), observers_.end(), obs) == observers_.end()) + << "Observers can only be added once!"; + observers_.push_back(obs); + } + + // Remove an observer from the list. + void RemoveObserver(ObserverType* obs) { + ListType::iterator it = find(observers_.begin(), observers_.end(), obs); + if (it != observers_.end()) { + if (notify_depth_) { + *it = 0; + } else { + observers_.erase(it); + } + } + } + + // An iterator class that can be used to access the list of observers. See + // also the FOREACH_OBSERVER macro defined below. + class Iterator { + public: + Iterator(const ObserverList<ObserverType>& list) : list_(list), index_(0) { + ++list_.notify_depth_; + } + + ~Iterator() { + if (--list_.notify_depth_ == 0) + list_.Compact(); + } + + ObserverType* GetNext() { + ListType& observers = list_.observers_; + // Advance if the current element is null + while (index_ < observers.size() && !observers[index_]) + ++index_; + return index_ < observers.size() ? observers[index_++] : NULL; + } + + private: + const ObserverList<ObserverType>& list_; + size_t index_; + }; + + private: + typedef std::vector<ObserverType*> ListType; + + void Compact() const { + ListType::iterator it = observers_.begin(); + while (it != observers_.end()) { + if (*it) { + ++it; + } else { + it = observers_.erase(it); + } + } + } + + // These are marked mutable to facilitate having NotifyAll be const. + mutable ListType observers_; + mutable int notify_depth_; + + friend class ObserverList::Iterator; + + DISALLOW_EVIL_CONSTRUCTORS(ObserverList); +}; + +#define FOR_EACH_OBSERVER(ObserverType, observer_list, func) \ + do { \ + ObserverList<ObserverType>::Iterator it(observer_list); \ + ObserverType* obs; \ + while (obs = it.GetNext()) \ + obs->func; \ + } while (0) + +#endif // BASE_OBSERVER_LIST_H__ diff --git a/base/observer_list_unittest.cc b/base/observer_list_unittest.cc new file mode 100644 index 0000000..6bb1d63 --- /dev/null +++ b/base/observer_list_unittest.cc @@ -0,0 +1,88 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/observer_list.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class ObserverListTest : public testing::Test { +}; + +class Foo { + public: + virtual void Observe(int x) = 0; +}; + +class Adder : public Foo { + public: + Adder(int scaler) : scaler_(scaler), total(0) {} + virtual void Observe(int x) { + total += x * scaler_; + } + int total; + private: + int scaler_; +}; + +class Disrupter : public Foo { + public: + Disrupter(ObserverList<Foo>& list, Foo* doomed) : list_(list), doomed_(doomed) { + } + virtual void Observe(int x) { + list_.RemoveObserver(doomed_); + } + private: + ObserverList<Foo>& list_; + Foo* doomed_; +}; + +} // namespace + +TEST(ObserverListTest, BasicTest) { + ObserverList<Foo> observer_list; + Adder a(1), b(-1), c(1), d(-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); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(10)); + + EXPECT_EQ(a.total, 20); + EXPECT_EQ(b.total, -20); + EXPECT_EQ(c.total, 0); + EXPECT_EQ(d.total, -10); +} diff --git a/base/path_service.cc b/base/path_service.cc new file mode 100644 index 0000000..f4b3a83 --- /dev/null +++ b/base/path_service.cc @@ -0,0 +1,202 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <shellapi.h> +#include <shlobj.h> + +#include <hash_map> +#include <hash_set> + +#include "base/path_service.h" + +#include "base/lock.h" +#include "base/logging.h" +#include "base/file_util.h" + +namespace base { + bool PathProvider(int key, std::wstring* result); +} + +namespace { + +typedef stdext::hash_map<int,std::wstring> PathMap; +typedef stdext::hash_set<int> PathSet; + +// 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 +}; + +static Provider base_provider = { + base::PathProvider, + NULL, +#ifndef NDEBUG + base::PATH_START, + base::PATH_END +#endif +}; + +struct PathData { + Lock lock; + PathMap cache; // Track mappings from path key to path value. + PathSet overrides; // Track whether a path has been overridden. + Provider* providers; // Linked list of path service providers. + + PathData() : providers(&base_provider) { + } +}; + +// We rely on the path service not being used prior to 'main' execution, and +// we are happy to let this data structure leak at process exit. +PathData* path_data = new PathData(); + +} // 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, std::wstring* result) { + DCHECK(path_data); + DCHECK(result); + DCHECK(key >= base::DIR_CURRENT); + + // special case the current directory because it can never be cached + if (key == base::DIR_CURRENT) { + 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; + *result = system_buffer; + file_util::TrimTrailingSeparator(result); + return true; + } + + // TODO(darin): it would be nice to avoid holding this lock while calling out + // to the path providers. + AutoLock scoped_lock(path_data->lock); + + // 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; + } + + std::wstring path; + + // search providers for the requested path + Provider* provider = path_data->providers; + 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; + + // Save the computed path in our cache. + path_data->cache[key] = path; + + result->swap(path); + return true; +} + +bool PathService::IsOverridden(int key) { + DCHECK(path_data); + + AutoLock scoped_lock(path_data->lock); + return path_data->overrides.find(key) != path_data->overrides.end(); +} + +bool PathService::Override(int key, const std::wstring& path) { + DCHECK(path_data); + DCHECK(key > base::DIR_CURRENT) << "invalid path key"; + + wchar_t file_path_buf[MAX_PATH]; + if (!_wfullpath(file_path_buf, path.c_str(), MAX_PATH)) + return false; + std::wstring file_path(file_path_buf); + + // make sure the directory exists: + if (!file_util::PathExists(file_path) && + // TODO(darin): what if this path is not that of a directory? + !file_util::CreateDirectory(file_path)) + return false; + + file_util::TrimTrailingSeparator(&file_path); + + AutoLock scoped_lock(path_data->lock); + path_data->cache[key] = file_path; + path_data->overrides.insert(key); + return true; +} + +bool PathService::SetCurrentDirectory(const std::wstring& current_directory) { + BOOL ret = ::SetCurrentDirectory(current_directory.c_str()); + return (ret ? true : false); +} + +void PathService::RegisterProvider(ProviderFunc func, int key_start, + int key_end) { + DCHECK(path_data); + DCHECK(key_end > key_start); + + AutoLock scoped_lock(path_data->lock); + + Provider* p; + +#ifndef NDEBUG + p = path_data->providers; + while (p) { + DCHECK(key_start >= p->key_end || key_end <= p->key_start) << + "path provider collision"; + p = p->next; + } +#endif + + p = new Provider; + p->func = func; + p->next = path_data->providers; +#ifndef NDEBUG + p->key_start = key_start; + p->key_end = key_end; +#endif + path_data->providers = p; +} diff --git a/base/path_service.h b/base/path_service.h new file mode 100644 index 0000000..861c9b5 --- /dev/null +++ b/base/path_service.h @@ -0,0 +1,85 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_PATH_SERVICE_H__ +#define BASE_PATH_SERVICE_H__ + +#include <string> + +#include "base/base_paths.h" + +// The path service is a global table mapping keys to file system paths. It is +// OK to use this service from multiple threads. +// +class 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, std::wstring* 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 std::wstring& path); + + // Return whether a path was overridden. + static bool IsOverridden(int key); + + // Sets the current directory. + static bool SetCurrentDirectory(const std::wstring& current_directory); + + // 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, std::wstring*); + + // 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); +}; + +#endif // BASE_PATH_SERVICE_H__ diff --git a/base/path_service_unittest.cc b/base/path_service_unittest.cc new file mode 100644 index 0000000..8840650 --- /dev/null +++ b/base/path_service_unittest.cc @@ -0,0 +1,57 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class PathServiceTest : public testing::Test { + }; +}; + +// 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) { + std::wstring path; + bool result = PathService::Get(dir_type, &path); + return result && !path.empty(); +} + +// 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(PathServiceTest, Get) { + for (int key = base::DIR_CURRENT; key < base::PATH_END; ++key) { + EXPECT_PRED1(ReturnsValidPath, key); + } +} diff --git a/base/pe_image.cc b/base/pe_image.cc new file mode 100644 index 0000000..b50bca5 --- /dev/null +++ b/base/pe_image.cc @@ -0,0 +1,560 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file implements PEImage, a generic class to manipulate PE files. +// This file was adapted from GreenBorder's Code. + +#include "base/pe_image.h" + +// Structure to perform imports enumerations. +struct EnumAllImportsStorage { + PEImage::EnumImportsFunction callback; + PVOID cookie; +}; + +// Callback used to enumerate imports. See EnumImportChunksFunction. +bool ProcessImportChunk(const PEImage &image, LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie) { + EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>( + cookie); + + return image.EnumOneImportChunk(storage.callback, module, name_table, iat, + storage.cookie); +} + +// Callback used to enumerate delay imports. See EnumDelayImportChunksFunction. +bool ProcessDelayImportChunk(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, PVOID cookie) { + EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>( + cookie); + + return image.EnumOneDelayImportChunk(storage.callback, delay_descriptor, + module, name_table, iat, bound_iat, + unload_iat, storage.cookie); +} + +void PEImage::set_module(HMODULE module) { + module_ = module; +} + +PIMAGE_DOS_HEADER PEImage::GetDosHeader() const { + return reinterpret_cast<PIMAGE_DOS_HEADER>(module_); +} + +PIMAGE_NT_HEADERS PEImage::GetNTHeaders() const { + PIMAGE_DOS_HEADER dos_header = GetDosHeader(); + + return reinterpret_cast<PIMAGE_NT_HEADERS>( + reinterpret_cast<char*>(dos_header) + dos_header->e_lfanew); +} + +PIMAGE_SECTION_HEADER PEImage::GetSectionHeader(UINT section) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + PIMAGE_SECTION_HEADER first_section = IMAGE_FIRST_SECTION(nt_headers); + + if (section < nt_headers->FileHeader.NumberOfSections) + return first_section + section; + else + return NULL; +} + +WORD PEImage::GetNumSections() const { + return GetNTHeaders()->FileHeader.NumberOfSections; +} + +DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + return nt_headers->OptionalHeader.DataDirectory[directory].Size; +} + +PVOID PEImage::GetImageDirectoryEntryAddr(UINT directory) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + return RVAToAddr( + nt_headers->OptionalHeader.DataDirectory[directory].VirtualAddress); +} + +PIMAGE_SECTION_HEADER PEImage::GetImageSectionFromAddr(PVOID address) const { + PBYTE target = reinterpret_cast<PBYTE>(address); + PIMAGE_SECTION_HEADER section; + + for (UINT i = 0; NULL != (section = GetSectionHeader(i)); i++) { + // Don't use the virtual RVAToAddr. + PBYTE start = reinterpret_cast<PBYTE>( + PEImage::RVAToAddr(section->VirtualAddress)); + + DWORD size = section->Misc.VirtualSize; + + if ((start <= target) && (start + size > target)) + return section; + } + + return NULL; +} + +PIMAGE_SECTION_HEADER PEImage::GetImageSectionHeaderByName( + LPCSTR section_name) const { + if (NULL == section_name) + return NULL; + + PIMAGE_SECTION_HEADER ret = NULL; + int num_sections = GetNumSections(); + + for (int i = 0; i < num_sections; i++) { + PIMAGE_SECTION_HEADER section = GetSectionHeader(i); + if (0 == _strnicmp(reinterpret_cast<LPCSTR>(section->Name), section_name, + sizeof(section->Name))) { + ret = section; + break; + } + } + + return ret; +} + +PDWORD PEImage::GetExportEntry(LPCSTR name) const { + PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); + + if (NULL == exports) + return NULL; + + WORD ordinal = 0; + if (!GetProcOrdinal(name, &ordinal)) + return NULL; + + PDWORD functions = reinterpret_cast<PDWORD>( + RVAToAddr(exports->AddressOfFunctions)); + + return functions + ordinal - exports->Base; +} + +FARPROC PEImage::GetProcAddress(LPCSTR function_name) const { + PDWORD export_entry = GetExportEntry(function_name); + if (NULL == export_entry) + return NULL; + + PBYTE function = reinterpret_cast<PBYTE>(RVAToAddr(*export_entry)); + + PBYTE exports = reinterpret_cast<PBYTE>( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); + + // Check for forwarded exports as a special case. + if (exports <= function && exports + size > function) +#pragma warning(push) +#pragma warning(disable: 4312) + // This cast generates a warning because it is 32 bit specific. + return reinterpret_cast<FARPROC>(0xFFFFFFFF); +#pragma warning(pop) + + return reinterpret_cast<FARPROC>(function); +} + +bool PEImage::GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const { + if (NULL == ordinal) + return false; + + PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); + + if (NULL == exports) + return false; + + if (IsOrdinal(function_name)) { + *ordinal = ToOrdinal(function_name); + } else { + PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames)); + PDWORD lower = names; + PDWORD upper = names + exports->NumberOfNames; + int cmp = -1; + + // Binary Search for the name. + while (lower != upper) { + PDWORD middle = lower + (upper - lower) / 2; + LPCSTR name = reinterpret_cast<LPCSTR>(RVAToAddr(*middle)); + + cmp = strcmp(function_name, name); + + if (cmp == 0) { + lower = middle; + break; + } + + if (cmp > 0) + lower = middle + 1; + else + upper = middle; + } + + if (cmp != 0) + return false; + + + PWORD ordinals = reinterpret_cast<PWORD>( + RVAToAddr(exports->AddressOfNameOrdinals)); + + *ordinal = ordinals[lower - names] + static_cast<WORD>(exports->Base); + } + + return true; +} + +bool PEImage::EnumSections(EnumSectionsFunction callback, PVOID cookie) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + UINT num_sections = nt_headers->FileHeader.NumberOfSections; + PIMAGE_SECTION_HEADER section = GetSectionHeader(0); + + for (UINT i = 0; i < num_sections; i++, section++) { + PVOID section_start = RVAToAddr(section->VirtualAddress); + DWORD size = section->Misc.VirtualSize; + + if (!callback(*this, section, section_start, size, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumExports(EnumExportsFunction callback, PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); + + // Check if there are any exports at all. + if (NULL == directory || 0 == size) + return true; + + PIMAGE_EXPORT_DIRECTORY exports = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>( + directory); + UINT ordinal_base = exports->Base; + UINT num_funcs = exports->NumberOfFunctions; + UINT num_names = exports->NumberOfNames; + PDWORD functions = reinterpret_cast<PDWORD>(RVAToAddr( + exports->AddressOfFunctions)); + PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames)); + PWORD ordinals = reinterpret_cast<PWORD>(RVAToAddr( + exports->AddressOfNameOrdinals)); + + for (UINT count = 0; count < num_funcs; count++) { + PVOID func = RVAToAddr(functions[count]); + if (NULL == func) + continue; + + // Check for a name. + LPCSTR name = NULL; + UINT hint; + for (hint = 0; hint < num_names; hint++) { + if (ordinals[hint] == count) { + name = reinterpret_cast<LPCSTR>(RVAToAddr(names[hint])); + break; + } + } + + if (name == NULL) + hint = 0; + + // Check for forwarded exports. + LPCSTR forward = NULL; + if (reinterpret_cast<char*>(func) >= reinterpret_cast<char*>(directory) && + reinterpret_cast<char*>(func) <= reinterpret_cast<char*>(directory) + + size) { + forward = reinterpret_cast<LPCSTR>(func); + func = 0; + } + + if (!callback(*this, ordinal_base + count, hint, name, func, forward, + cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_BASERELOC); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC); + PIMAGE_BASE_RELOCATION base = reinterpret_cast<PIMAGE_BASE_RELOCATION>( + directory); + + if (directory == NULL || size < sizeof(IMAGE_BASE_RELOCATION)) + return true; + + while (base->SizeOfBlock) { + PWORD reloc = reinterpret_cast<PWORD>(base + 1); + UINT num_relocs = (base->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / + sizeof(WORD); + + for (UINT i = 0; i < num_relocs; i++, reloc++) { + WORD type = *reloc >> 12; + PVOID address = RVAToAddr(base->VirtualAddress + (*reloc & 0x0FFF)); + + if (!callback(*this, type, address, cookie)) + return false; + } + + base = reinterpret_cast<PIMAGE_BASE_RELOCATION>( + reinterpret_cast<char*>(base) + base->SizeOfBlock); + } + + return true; +} + +bool PEImage::EnumImportChunks(EnumImportChunksFunction callback, + PVOID cookie) const { + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT); + PIMAGE_IMPORT_DESCRIPTOR import = GetFirstImportChunk(); + + if (import == NULL || size < sizeof(IMAGE_IMPORT_DESCRIPTOR)) + return true; + + for (; import->FirstThunk; import++) { + LPCSTR module_name = reinterpret_cast<LPCSTR>(RVAToAddr(import->Name)); + PIMAGE_THUNK_DATA name_table = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(import->OriginalFirstThunk)); + PIMAGE_THUNK_DATA iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(import->FirstThunk)); + + if (!callback(*this, module_name, name_table, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumOneImportChunk(EnumImportsFunction callback, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie) const { + if (NULL == name_table) + return false; + + for (; name_table && name_table->u1.Ordinal; name_table++, iat++) { + LPCSTR name = NULL; + WORD ordinal = 0; + WORD hint = 0; + + if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { + ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal)); + } else { + PIMAGE_IMPORT_BY_NAME import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>( + RVAToAddr(name_table->u1.ForwarderString)); + + hint = import->Hint; + name = reinterpret_cast<LPCSTR>(&import->Name); + } + + if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie) const { + EnumAllImportsStorage temp = { callback, cookie }; + return EnumImportChunks(ProcessImportChunk, &temp); +} + +bool PEImage::EnumDelayImportChunks(EnumDelayImportChunksFunction callback, + PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr( + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + PImgDelayDescr delay_descriptor = reinterpret_cast<PImgDelayDescr>(directory); + + if (directory == NULL || size == 0) + return true; + + for (; delay_descriptor->rvaHmod; delay_descriptor++) { + PIMAGE_THUNK_DATA name_table; + PIMAGE_THUNK_DATA iat; + PIMAGE_THUNK_DATA bound_iat; // address of the optional bound IAT + PIMAGE_THUNK_DATA unload_iat; // address of optional copy of original IAT + LPCSTR module_name; + + // check if VC7-style imports, using RVAs instead of + // VC6-style addresses. + bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; + + if (rvas) { + module_name = reinterpret_cast<LPCSTR>( + RVAToAddr(delay_descriptor->rvaDLLName)); + name_table = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaINT)); + iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaIAT)); + bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaBoundIAT)); + unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaUnloadIAT)); + } else { +#pragma warning(push) +#pragma warning(disable: 4312) + // These casts generate warnings because they are 32 bit specific. + module_name = reinterpret_cast<LPCSTR>(delay_descriptor->rvaDLLName); + name_table = reinterpret_cast<PIMAGE_THUNK_DATA>( + delay_descriptor->rvaINT); + iat = reinterpret_cast<PIMAGE_THUNK_DATA>(delay_descriptor->rvaIAT); + bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + delay_descriptor->rvaBoundIAT); + unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + delay_descriptor->rvaUnloadIAT); +#pragma warning(pop) + } + + if (!callback(*this, delay_descriptor, module_name, name_table, iat, + bound_iat, unload_iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumOneDelayImportChunk(EnumImportsFunction callback, + PImgDelayDescr delay_descriptor, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) const { + UNREFERENCED_PARAMETER(bound_iat); + UNREFERENCED_PARAMETER(unload_iat); + + for (; name_table->u1.Ordinal; name_table++, iat++) { + LPCSTR name = NULL; + WORD ordinal = 0; + WORD hint = 0; + + if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { + ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal)); + } else { + PIMAGE_IMPORT_BY_NAME import; + bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; + + if (rvas) { + import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>( + RVAToAddr(name_table->u1.ForwarderString)); + } else { +#pragma warning(push) +#pragma warning(disable: 4312) + // This cast generates a warning because it is 32 bit specific. + import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>( + name_table->u1.ForwarderString); +#pragma warning(pop) + } + + hint = import->Hint; + name = reinterpret_cast<LPCSTR>(&import->Name); + } + + if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumAllDelayImports(EnumImportsFunction callback, + PVOID cookie) const { + EnumAllImportsStorage temp = { callback, cookie }; + return EnumDelayImportChunks(ProcessDelayImportChunk, &temp); +} + +bool PEImage::VerifyMagic() const { + PIMAGE_DOS_HEADER dos_header = GetDosHeader(); + + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return false; + + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + if (nt_headers->Signature != IMAGE_NT_SIGNATURE) + return false; + + if (nt_headers->FileHeader.SizeOfOptionalHeader != + sizeof(IMAGE_OPTIONAL_HEADER)) + return false; + + if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) + return false; + + return true; +} + +bool PEImage::ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const { + LPVOID address = RVAToAddr(rva); + return ImageAddrToOnDiskOffset(address, on_disk_offset); +} + +bool PEImage::ImageAddrToOnDiskOffset(LPVOID address, + DWORD *on_disk_offset) const { + if (NULL == address) + return false; + + // Get the section that this address belongs to. + PIMAGE_SECTION_HEADER section_header = GetImageSectionFromAddr(address); + if (NULL == section_header) + return false; + +#pragma warning(push) +#pragma warning(disable: 4311) + // These casts generate warnings because they are 32 bit specific. + // Don't follow the virtual RVAToAddr, use the one on the base. + DWORD offset_within_section = reinterpret_cast<DWORD>(address) - + reinterpret_cast<DWORD>(PEImage::RVAToAddr( + section_header->VirtualAddress)); +#pragma warning(pop) + + *on_disk_offset = section_header->PointerToRawData + offset_within_section; + return true; +} + +PVOID PEImage::RVAToAddr(DWORD rva) const { + if (rva == 0) + return NULL; + + return reinterpret_cast<char*>(module_) + rva; +} + +PVOID PEImageAsData::RVAToAddr(DWORD rva) const { + if (rva == 0) + return NULL; + + PVOID in_memory = PEImage::RVAToAddr(rva); + DWORD dummy; + + if (!ImageAddrToOnDiskOffset(in_memory, &dummy)) + return NULL; + + return in_memory; +} diff --git a/base/pe_image.h b/base/pe_image.h new file mode 100644 index 0000000..a8ff941 --- /dev/null +++ b/base/pe_image.h @@ -0,0 +1,282 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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_SRC_PE_IMAGE_H__ +#define BASE_SRC_PE_IMAGE_H__ + +#include <windows.h> +#include <DelayIMP.h> + +// This class is a wrapper for the Portable Executable File Format (PE). +// It's main purpose is to provide an easy way to work with imports and exports +// from a file, mapped in memory as image. +class PEImage { + public: + // Callback to enumerate sections. + // cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumSectionsFunction)(const PEImage &image, + PIMAGE_SECTION_HEADER header, + PVOID section_start, DWORD section_size, + PVOID cookie); + + // Callback to enumerate exports. + // function is the actual address of the symbol. If forward is not null, it + // contains the dll and symbol to forward this export to. cookie is the value + // passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumExportsFunction)(const PEImage &image, DWORD ordinal, + DWORD hint, LPCSTR name, PVOID function, + LPCSTR forward, PVOID cookie); + + // Callback to enumerate import blocks. + // name_table and iat point to the imports name table and address table for + // this block. cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumImportChunksFunction)(const PEImage &image, LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie); + + // Callback to enumerate imports. + // module is the dll that exports this symbol. cookie is the value passed to + // the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumImportsFunction)(const PEImage &image, LPCSTR module, + DWORD ordinal, LPCSTR name, DWORD hint, + PIMAGE_THUNK_DATA iat, PVOID cookie); + + // Callback to enumerate dalayed import blocks. + // module is the dll that exports this block of symbols. cookie is the value + // passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumDelayImportChunksFunction)(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie); + + // Callback to enumerate relocations. + // cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumRelocsFunction)(const PEImage &image, WORD type, + PVOID address, PVOID cookie); + + explicit PEImage(HMODULE module) : module_(module) {} + explicit PEImage(const void* module) { + module_ = reinterpret_cast<HMODULE>(const_cast<void*>(module)); + } + + // Gets the HMODULE for this object. + HMODULE module() const; + + // Sets this object's HMODULE. + void set_module(HMODULE module); + + // Checks if this symbol is actually an ordinal. + static bool IsOrdinal(LPCSTR name); + + // Converts a named symbol to the corresponding ordinal. + static WORD ToOrdinal(LPCSTR name); + + // Returns the DOS_HEADER for this PE. + PIMAGE_DOS_HEADER GetDosHeader() const; + + // Returns the NT_HEADER for this PE. + PIMAGE_NT_HEADERS GetNTHeaders() const; + + // Returns number of sections of this PE. + WORD GetNumSections() const; + + // Returns the header for a given section. + // returns NULL if there is no such section. + PIMAGE_SECTION_HEADER GetSectionHeader(UINT section) const; + + // Returns the size of a given directory entry. + DWORD GetImageDirectoryEntrySize(UINT directory) const; + + // Returns the address of a given directory entry. + PVOID GetImageDirectoryEntryAddr(UINT directory) const; + + // Returns the section header for a given address. + // Use: s = image.GetImageSectionFromAddr(a); + // Post: 's' is the section header of the section that contains 'a' + // or NULL if there is no such section. + PIMAGE_SECTION_HEADER GetImageSectionFromAddr(PVOID address) const; + + // Returns the section header for a given section. + PIMAGE_SECTION_HEADER GetImageSectionHeaderByName(LPCSTR section_name) const; + + // Returns the first block of imports. + PIMAGE_IMPORT_DESCRIPTOR GetFirstImportChunk() const; + + // Returns the exports directory. + PIMAGE_EXPORT_DIRECTORY GetExportDirectory() const; + + // Returns a given export entry. + // Use: e = image.GetExportEntry(f); + // Pre: 'f' is either a zero terminated string or ordinal + // Post: 'e' is a pointer to the export directory entry + // that contains 'f's export RVA, or NULL if 'f' + // is not exported from this image + PDWORD GetExportEntry(LPCSTR name) const; + + // Returns the address for a given exported symbol. + // Use: p = image.GetProcAddress(f); + // Pre: 'f' is either a zero terminated string or ordinal. + // Post: if 'f' is a non-forwarded export from image, 'p' is + // the exported function. If 'f' is a forwarded export + // then p is the special value 0xFFFFFFFF. In this case + // RVAToAddr(*GetExportEntry) can be used to resolve + // the string that describes the forward. + FARPROC GetProcAddress(LPCSTR function_name) const; + + // Retrieves the ordinal for a given exported symbol. + // Returns true if the symbol was found. + bool GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const; + + // Enumerates PE sections. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumSections(EnumSectionsFunction callback, PVOID cookie) const; + + // Enumerates PE exports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumExports(EnumExportsFunction callback, PVOID cookie) const; + + // Enumerates PE imports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumAllImports(EnumImportsFunction callback, PVOID cookie) const; + + // Enumerates PE import blocks. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumImportChunks(EnumImportChunksFunction callback, PVOID cookie) const; + + // Enumerates the imports from a single PE import block. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumOneImportChunk(EnumImportsFunction callback, LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, PIMAGE_THUNK_DATA iat, + PVOID cookie) const; + + + // Enumerates PE delay imports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumAllDelayImports(EnumImportsFunction callback, PVOID cookie) const; + + // Enumerates PE delay import blocks. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumDelayImportChunks(EnumDelayImportChunksFunction callback, + PVOID cookie) const; + + // Enumerates imports from a single PE delay import block. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumOneDelayImportChunk(EnumImportsFunction callback, + PImgDelayDescr delay_descriptor, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) const; + + // Enumerates PE relocation entries. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const; + + // Verifies the magic values on the PE file. + // Returns true if all values are correct. + bool VerifyMagic() const; + + // Converts an rva value to the appropriate address. + virtual PVOID RVAToAddr(DWORD rva) const; + + // Converts an rva value to an offset on disk. + // Returns true on success. + bool ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const; + + // Converts an address to an offset on disk. + // Returns true on success. + bool ImageAddrToOnDiskOffset(LPVOID address, DWORD *on_disk_offset) const; + + private: + HMODULE module_; +}; + +// This class is an extension to the PEImage class that allows working with PE +// files mapped as data instead of as image file. +class PEImageAsData : public PEImage { + public: + explicit PEImageAsData(HMODULE hModule) : PEImage(hModule) {} + + virtual PVOID RVAToAddr(DWORD rva) const; +}; + +inline bool PEImage::IsOrdinal(LPCSTR name) { +#pragma warning(push) +#pragma warning(disable: 4311) + // This cast generates a warning because it is 32 bit specific. + return reinterpret_cast<DWORD>(name) <= 0xFFFF; +#pragma warning(pop) +} + +inline WORD PEImage::ToOrdinal(LPCSTR name) { + return reinterpret_cast<WORD>(name); +} + +inline HMODULE PEImage::module() const { + return module_; +} + +inline PIMAGE_IMPORT_DESCRIPTOR PEImage::GetFirstImportChunk() const { + return reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_IMPORT)); +} + +inline PIMAGE_EXPORT_DIRECTORY PEImage::GetExportDirectory() const { + return reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); +} + +#endif // BASE_SRC_PE_IMAGE_H__ diff --git a/base/pe_image_unittest.cc b/base/pe_image_unittest.cc new file mode 100644 index 0000000..31c99a5 --- /dev/null +++ b/base/pe_image_unittest.cc @@ -0,0 +1,226 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file contains unit tests for PEImage. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/pe_image.h" + +// Just counts the number of invocations. +bool ExportsCallback(const PEImage &image, + DWORD ordinal, + DWORD hint, + LPCSTR name, + PVOID function, + LPCSTR forward, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool ImportsCallback(const PEImage &image, + LPCSTR module, + DWORD ordinal, + LPCSTR name, + DWORD hint, + PIMAGE_THUNK_DATA iat, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool SectionsCallback(const PEImage &image, + PIMAGE_SECTION_HEADER header, + PVOID section_start, + DWORD section_size, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool RelocsCallback(const PEImage &image, + WORD type, + PVOID address, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool ImportChunksCallback(const PEImage &image, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool DelayImportChunksCallback(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// We'll be using some known values for the tests. +enum Value { + sections = 0, + imports_dlls, + delay_dlls, + exports, + imports, + delay_imports, + relocs +}; + +// 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; + + // Contains the expected value, for each enumerated property (Value), and the + // OS version: [Value][os_version] + const int expected[][3] = { + {4, 4, 4}, + {3, 3, 3}, + {w2k_delay_dlls, xp_delay_dlls, vista_delay_dlls}, + {w2k_exports, xp_exports, vista_exports}, + {w2k_imports, xp_imports, vista_imports}, + {w2k_delay_imports, xp_delay_imports, vista_delay_imports}, + {w2k_relocs, xp_relocs, vista_relocs} + }; + + if (value > relocs) + return 0; + if (50 == os) + os = 0; // 5.0 + else if (51 == os || 52 == os) + os = 1; + else if (os >= 60) + os = 2; // 6.x + else + return 0; + + return expected[value][os]; +} + +// Tests that we are able to enumerate stuff from a PE file, and that +// the actual number of items found is within the expected range. +TEST(PEImageTest, EnumeratesPE) { + HMODULE module = LoadLibrary(L"advapi32.dll"); + ASSERT_TRUE(NULL != module); + + PEImage pe(module); + int count = 0; + EXPECT_TRUE(pe.VerifyMagic()); + + DWORD os = pe.GetNTHeaders()->OptionalHeader.MajorOperatingSystemVersion; + os = os * 10 + pe.GetNTHeaders()->OptionalHeader.MinorOperatingSystemVersion; + + pe.EnumSections(SectionsCallback, &count); + EXPECT_EQ(GetExpectedValue(sections, os), count); + + count = 0; + pe.EnumImportChunks(ImportChunksCallback, &count); + EXPECT_EQ(GetExpectedValue(imports_dlls, os), count); + + count = 0; + pe.EnumDelayImportChunks(DelayImportChunksCallback, &count); + EXPECT_EQ(GetExpectedValue(delay_dlls, os), count); + + count = 0; + pe.EnumExports(ExportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(exports, os) - 20); + EXPECT_LT(count, GetExpectedValue(exports, os) + 100); + + count = 0; + pe.EnumAllImports(ImportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(imports, os) - 20); + EXPECT_LT(count, GetExpectedValue(imports, os) + 100); + + count = 0; + pe.EnumAllDelayImports(ImportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(delay_imports, os) - 2); + EXPECT_LT(count, GetExpectedValue(delay_imports, os) + 8); + + count = 0; + pe.EnumRelocs(RelocsCallback, &count); + EXPECT_GT(count, GetExpectedValue(relocs, os) - 150); + EXPECT_LT(count, GetExpectedValue(relocs, os) + 1500); + + FreeLibrary(module); +} + +// Tests that we can locate an specific exported symbol, by name and by ordinal. +TEST(PEImageTest, RetrievesExports) { + HMODULE module = LoadLibrary(L"advapi32.dll"); + ASSERT_TRUE(NULL != module); + + PEImage pe(module); + WORD ordinal; + + EXPECT_TRUE(pe.GetProcOrdinal("RegEnumKeyExW", &ordinal)); + + FARPROC address1 = pe.GetProcAddress("RegEnumKeyExW"); + FARPROC address2 = pe.GetProcAddress(reinterpret_cast<char*>(ordinal)); + EXPECT_TRUE(address1 != NULL); + EXPECT_TRUE(address2 != NULL); + EXPECT_TRUE(address1 == address2); + + FreeLibrary(module); +} diff --git a/base/perftimer.cc b/base/perftimer.cc new file mode 100644 index 0000000..442c4d3 --- /dev/null +++ b/base/perftimer.cc @@ -0,0 +1,69 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdio.h> +#include <shlwapi.h> +#include <windows.h> + +#include "base/perftimer.h" + +#include "base/logging.h" +#include "base/basictypes.h" + +static FILE* perf_log_file = NULL; + +bool InitPerfLog(const char* log_file) { + if (perf_log_file) { + // trying to initialize twice + NOTREACHED(); + return false; + } + + return fopen_s(&perf_log_file, log_file, "w") == 0; +} + +void FinalizePerfLog() { + if (!perf_log_file) { + // trying to cleanup without initializing + NOTREACHED(); + return; + } + fclose(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 0000000..03a7713 --- /dev/null +++ b/base/perftimer.h @@ -0,0 +1,105 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_PERFTTIMER_H__ +#define BASE_PERFTTIMER_H__ + +#include <string> +#include "base/basictypes.h" +#include "base/time.h" + +// ---------------------------------------------------------------------- +// 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 char* log_file); +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_ = TimeTicks::Now(); + } + + // Returns the time elapsed since object construction + TimeDelta Elapsed() const { + return TimeTicks::Now() - begin_; + } + + private: + 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_PERFTTIMER_H__ diff --git a/base/pickle.cc b/base/pickle.cc new file mode 100644 index 0000000..a16368b --- /dev/null +++ b/base/pickle.cc @@ -0,0 +1,348 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdlib.h> +#include <string> + +#include "base/pickle.h" + +//------------------------------------------------------------------------------ + +// static +const int Pickle::kPayloadUnit = 64; + +// 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(header_size >= sizeof(Header)); + DCHECK(header_size <= kPayloadUnit); + Resize(kPayloadUnit); + header_->payload_size = 0; +} + +Pickle::Pickle(const char* data, int data_len) + : header_(reinterpret_cast<Header*>(const_cast<char*>(data))), + header_size_(data_len - header_->payload_size), + capacity_(-1), + variable_buffer_offset_(0) { + DCHECK(header_size_ >= sizeof(Header)); + DCHECK(header_size_ == AlignInt(header_size_, sizeof(uint32))); +} + +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_ != -1) + free(header_); +} + +Pickle& Pickle::operator=(const Pickle& other) { + if (header_size_ != other.header_size_ && capacity_ != -1) { + 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_, header_size_ + other.header_->payload_size); + variable_buffer_offset_ = other.variable_buffer_offset_; + return *this; +} + +bool Pickle::ReadBool(void** iter, bool* result) const { + DCHECK(iter); + + int tmp; + if (!ReadInt(iter, &tmp)) + return false; + DCHECK(0 == tmp || 1 == tmp); + *result = tmp ? true : false; + return true; +} + +bool Pickle::ReadInt(void** iter, int* result) const { + DCHECK(iter); + if (!*iter) + *iter = const_cast<char*>(payload()); + + if (!IteratorHasRoomFor(*iter, sizeof(*result))) + return false; + + // TODO(jar) bug 1129285: Pickle should be cleaned up, and not dependent on + // alignment. + // Next line is otherwise the same as: memcpy(result, *iter, sizeof(*result)); + *result = *reinterpret_cast<int*>(*iter); + + UpdateIter(iter, sizeof(*result)); + return true; +} + +bool Pickle::ReadLength(void** iter, int* result) const { + if (!ReadInt(iter, result)) + return false; + return ((*result) >= 0); +} + +bool Pickle::ReadSize(void** iter, size_t* result) const { + DCHECK(iter); + if (!*iter) + *iter = const_cast<char*>(payload()); + + if (!IteratorHasRoomFor(*iter, sizeof(*result))) + return false; + + // TODO(jar) bug 1129285: Pickle should be cleaned up, and not dependent on + // alignment. + // Next line is otherwise the same as: memcpy(result, *iter, sizeof(*result)); + *result = *reinterpret_cast<size_t*>(*iter); + + UpdateIter(iter, sizeof(*result)); + return true; +} + +bool Pickle::ReadInt64(void** iter, int64* result) const { + DCHECK(iter); + if (!*iter) + *iter = const_cast<char*>(payload()); + + if (!IteratorHasRoomFor(*iter, sizeof(*result))) + return false; + + memcpy(result, *iter, sizeof(*result)); + + UpdateIter(iter, sizeof(*result)); + return true; +} + +bool Pickle::ReadIntPtr(void** iter, intptr_t* result) const { + DCHECK(iter); + if (!*iter) + *iter = const_cast<char*>(payload()); + + if (!IteratorHasRoomFor(*iter, sizeof(*result))) + return false; + + memcpy(result, *iter, sizeof(*result)); + + UpdateIter(iter, sizeof(*result)); + return true; +} + +bool Pickle::ReadString(void** iter, std::string* result) const { + DCHECK(iter); + + int len; + if (!ReadLength(iter, &len)) + return false; + if (!IteratorHasRoomFor(*iter, len)) + return false; + + char* chars = reinterpret_cast<char*>(*iter); + result->assign(chars, len); + + UpdateIter(iter, len); + return true; +} + +bool Pickle::ReadWString(void** iter, std::wstring* result) const { + DCHECK(iter); + + int len; + if (!ReadLength(iter, &len)) + return false; + if (!IteratorHasRoomFor(*iter, len * sizeof(wchar_t))) + return false; + + wchar_t* chars = reinterpret_cast<wchar_t*>(*iter); + result->assign(chars, len); + + UpdateIter(iter, len * sizeof(wchar_t)); + return true; +} + +bool Pickle::ReadBytes(void** iter, const char** data, int length) const { + DCHECK(iter); + DCHECK(data); + + if (!IteratorHasRoomFor(*iter, length)) + return false; + + *data = reinterpret_cast<const char*>(*iter); + + UpdateIter(iter, length); + return true; +} + +bool Pickle::ReadData(void** iter, const char** data, int* length) const { + DCHECK(iter); + DCHECK(data); + DCHECK(length); + + if (!ReadLength(iter, length)) + return false; + + return ReadBytes(iter, data, *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; + if (header_size_ + new_size > capacity_ && !Resize(header_size_ + new_size)) + return NULL; + + header_->payload_size = new_size; + return payload() + offset; +} + +void Pickle::EndWrite(char* dest, int length) { + // Zero-pad to keep tools like purify from complaining about uninitialized + // memory. + if (length % sizeof(uint32)) + memset(dest + length, 0, sizeof(uint32) - (length % sizeof(uint32))); +} + +bool Pickle::WriteBytes(const void* data, int data_len) { + DCHECK(capacity_ != -1) << "oops: pickle is readonly"; + + char* dest = BeginWrite(data_len); + if (!dest) + return false; + + memcpy(dest, data, data_len); + + EndWrite(dest, data_len); + return true; +} + +bool Pickle::WriteString(const std::string& value) { + if (!WriteInt(static_cast<int>(value.size()))) + return false; + + return WriteBytes(value.data(), static_cast<int>(value.size())); +} + +bool Pickle::WriteWString(const std::wstring& value) { + if (!WriteInt(static_cast<int>(value.size()))) + return false; + + return WriteBytes(value.data(), + static_cast<int>(value.size() * sizeof(value.data()[0]))); +} + +bool Pickle::WriteData(const char* data, int length) { + return WriteInt(length) && WriteBytes(data, length); +} + +char* Pickle::BeginWriteData(int length) { + DCHECK_EQ(variable_buffer_offset_, 0) << + "There can only be one variable buffer in a Pickle"; + + if (!WriteInt(length)) + return false; + + char *data_ptr = BeginWrite(length); + if (!data_ptr) + return NULL; + + variable_buffer_offset_ = + data_ptr - reinterpret_cast<char*>(header_) - sizeof(int); + + // EndWrite doesn't necessarily have to be called after the write operation, + // so we call it here to pad out what the caller will eventually write. + EndWrite(data_ptr, length); + return data_ptr; +} + +void Pickle::TrimWriteData(int length) { + DCHECK(variable_buffer_offset_ != 0); + + VariableLengthBuffer *buffer = reinterpret_cast<VariableLengthBuffer*>( + reinterpret_cast<char*>(header_) + variable_buffer_offset_); + + DCHECK_GE(buffer->length, length); + + int old_length = buffer->length; + int trimmed_bytes = old_length - length; + if (trimmed_bytes > 0) { + header_->payload_size -= trimmed_bytes; + buffer->length = length; + } +} + +bool Pickle::Resize(size_t new_capacity) { + new_capacity = AlignInt(new_capacity, kPayloadUnit); + + void* p = realloc(header_, new_capacity); + if (!p) + return false; + + header_ = reinterpret_cast<Header*>(p); + capacity_ = new_capacity; + return true; +} + +// static +const char* Pickle::FindNext(size_t header_size, + const char* start, + const char* end) { + DCHECK(header_size == AlignInt(header_size, sizeof(uint32))); + DCHECK(header_size <= kPayloadUnit); + + const Header* hdr = reinterpret_cast<const Header*>(start); + const char* payload_base = start + header_size; + const char* payload_end = payload_base + hdr->payload_size; + if (payload_end < payload_base) + return NULL; + + return (payload_end > end) ? NULL : payload_end; +} diff --git a/base/pickle.h b/base/pickle.h new file mode 100644 index 0000000..5e97ff5 --- /dev/null +++ b/base/pickle.h @@ -0,0 +1,259 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_PICKLE_H__ +#define BASE_PICKLE_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +// 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 Pickle { + public: + ~Pickle(); + + // 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); + + // Performs a deep copy. + Pickle& operator=(const Pickle& other); + + // Returns the size of the Pickle's data. + int size() const { return static_cast<int>(header_size_ + + header_->payload_size); } + + // Returns the data for this Pickle. + const void* data() const { return header_; } + + // Methods for reading the payload of the Pickle. To read from the start of + // the Pickle, initialize *iter to NULL. If successful, these methods return + // true. Otherwise, false is returned to indicate that the result could not + // be extracted. + bool ReadBool(void** iter, bool* result) const; + bool ReadInt(void** iter, int* result) const; + bool ReadSize(void** iter, size_t* result) const; + bool ReadInt64(void** iter, int64* result) const; + bool ReadIntPtr(void** iter, intptr_t* result) const; + bool ReadString(void** iter, std::string* result) const; + bool ReadWString(void** iter, std::wstring* result) const; + bool ReadData(void** iter, const char** data, int* length) const; + bool ReadBytes(void** iter, const char** data, int length) const; + + // Safer version of ReadInt() checks for the result not being negative. + // Use it for reading the object sizes. + bool Pickle::ReadLength(void** iter, int* result) const; + + // 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)); + } + bool WriteSize(size_t value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteInt64(int64 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteIntPtr(intptr_t value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteString(const std::string& value); + bool WriteWString(const std::wstring& value); + bool WriteData(const char* data, int length); + 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 { + size_t payload_size; // specifies the size of the payload + }; + + // Returns the header, cast to a user-specified type T. The type T must be a + // subclass of Header and its size must correspond to the header_size passed + // to the Pickle constructor. + template <class T> + T* headerT() { + DCHECK(sizeof(T) == header_size_); + return static_cast<T*>(header_); + } + template <class T> + const T* headerT() const { + DCHECK(sizeof(T) == header_size_); + return static_cast<const T*>(header_); + } + + // Returns true if the given iterator could point to data with the given + // length. If there is no room for the given data before the end of the + // payload, returns false. + bool IteratorHasRoomFor(const void* iter, int len) const { + if ((len < 0) || (iter < header_) || iter > end_of_payload()) + return false; + const char* end_of_region = reinterpret_cast<const char*>(iter) + len; + // Watch out for overflow in pointer calculation, which wraps. + return (iter <= end_of_region) && (end_of_region <= end_of_payload()); + } + + protected: + size_t payload_size() const { return header_->payload_size; } + + char* payload() { + return reinterpret_cast<char*>(header_) + header_size_; + } + const char* payload() const { + return reinterpret_cast<const char*>(header_) + header_size_; + } + + // Returns the address of the byte immediately following the currently valid + // header + payload. + char* end_of_payload() { + return payload() + payload_size(); + } + const char* end_of_payload() const { + return payload() + payload_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 inline size_t AlignInt(size_t i, int alignment) { + return i + (alignment - (i % alignment)) % alignment; + } + + // Moves the iterator by the given number of bytes, making sure it is aligned. + // Pointer (iterator) is NOT aligned, but the change in the pointer + // is guaranteed to be a multiple of sizeof(uint32). + static inline void UpdateIter(void** iter, int bytes) { + *iter = static_cast<char*>(*iter) + AlignInt(bytes, sizeof(uint32)); + } + + // 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: + // A buffer of variable length; used internally + struct VariableLengthBuffer { + int length; + char data; // This is variable length. + }; + + 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(PickleTest, Resize); + FRIEND_TEST(PickleTest, FindNext); + FRIEND_TEST(PickleTest, IteratorHasRoom); +}; + +#endif // BASE_PICKLE_H__ diff --git a/base/pickle_unittest.cc b/base/pickle_unittest.cc new file mode 100644 index 0000000..6780e21 --- /dev/null +++ b/base/pickle_unittest.cc @@ -0,0 +1,238 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <windows.h> +#include <string.h> + +#include "base/basictypes.h" +#include "base/check_handler.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/pickle.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; + +// checks that the result +void VerifyResult(const Pickle& pickle) { + void* iter = NULL; + + int outint; + EXPECT_TRUE(pickle.ReadInt(&iter, &outint)); + EXPECT_EQ(testint, outint); + + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ(teststr, outstr); + + std::wstring outwstr; + EXPECT_TRUE(pickle.ReadWString(&iter, &outwstr)); + EXPECT_EQ(testwstr, outwstr); + + bool outbool; + EXPECT_TRUE(pickle.ReadBool(&iter, &outbool)); + EXPECT_EQ(testbool1, outbool); + EXPECT_TRUE(pickle.ReadBool(&iter, &outbool)); + EXPECT_EQ(testbool2, outbool); + + 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.WriteData(testdata, testdatalen)); + + char* dest = pickle.BeginWriteData(testdatalen); + EXPECT_TRUE(dest); + memcpy(dest, testdata, testdatalen); + + VerifyResult(pickle); + + // test copy constructor + Pickle pickle2(pickle); + VerifyResult(pickle2); + + // test operator= + Pickle pickle3; + pickle3 = pickle; + VerifyResult(pickle3); +} + +TEST(PickleTest, ZeroLenStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteString("")); + + void* iter = NULL; + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ("", outstr); +} + +TEST(PickleTest, ZeroLenWStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteWString(L"")); + + void* iter = NULL; + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ("", outstr); +} + +TEST(PickleTest, BadLenStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(-2)); + + void* iter = NULL; + std::string outstr; + EXPECT_FALSE(pickle.ReadString(&iter, &outstr)); +} + +TEST(PickleTest, BadLenWStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(-1)); + + void* iter = NULL; + std::wstring woutstr; + EXPECT_FALSE(pickle.ReadWString(&iter, &woutstr)); +} + +TEST(PickleTest, FindNext) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(1)); + EXPECT_TRUE(pickle.WriteString("Domo")); + + const char* start = reinterpret_cast<const char*>(pickle.data()); + const char* end = start + pickle.size(); + + EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end)); + EXPECT_TRUE(NULL == Pickle::FindNext(pickle.header_size_, start, end - 1)); + EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end + 1)); +} + +TEST(PickleTest, IteratorHasRoom) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(1)); + EXPECT_TRUE(pickle.WriteInt(2)); + + const void* iter = 0; + EXPECT_FALSE(pickle.IteratorHasRoomFor(iter, 1)); + iter = pickle.payload(); + EXPECT_TRUE(pickle.IteratorHasRoomFor(iter, 0)); + EXPECT_TRUE(pickle.IteratorHasRoomFor(iter, 1)); + EXPECT_FALSE(pickle.IteratorHasRoomFor(iter, -1)); + EXPECT_TRUE(pickle.IteratorHasRoomFor(iter, sizeof(int) * 2)); + EXPECT_FALSE(pickle.IteratorHasRoomFor(iter, (sizeof(int) * 2) + 1)); +} + +TEST(PickleTest, Resize) { + int unit = Pickle::kPayloadUnit; + scoped_array<char> data(new char[unit]); + char* data_ptr = data.get(); + for (int 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 int payload_size_after_header = unit - sizeof(uint32); + Pickle pickle; + pickle.WriteData(data_ptr, payload_size_after_header - sizeof(uint32)); + int cur_payload = payload_size_after_header; + + EXPECT_EQ(pickle.capacity(), unit); + EXPECT_EQ(pickle.payload_size(), payload_size_after_header); + + // fill out a full page (noting data header) + pickle.WriteData(data_ptr, unit - sizeof(uint32)); + cur_payload += unit; + EXPECT_EQ(unit*2, pickle.capacity()); + EXPECT_EQ(cur_payload, pickle.payload_size()); + + // one more byte should expand the capacity by one unit + pickle.WriteData(data_ptr, 1); + cur_payload += 5; + EXPECT_EQ(unit * 3, pickle.capacity()); + EXPECT_EQ(cur_payload, pickle.payload_size()); +} + +TEST(PickleTest, HeaderPadding) { + struct CustomHeader : Pickle::Header { + int blah; + }; + + const uint32 kMagic = 0x12345678; + + Pickle pickle(sizeof(CustomHeader)); + pickle.WriteInt(kMagic); + + // this should not overwrite the 'int' payload + pickle.headerT<CustomHeader>()->blah = 10; + + void* iter = NULL; + int result; + ASSERT_TRUE(pickle.ReadInt(&iter, &result)); + + EXPECT_EQ(result, kMagic); +} + +TEST(PickleTest, EqualsOperator) { + Pickle source; + source.WriteInt(1); + + Pickle copy_refs_source_buffer(static_cast<const char*>(source.data()), + source.size()); + Pickle copy; + copy = copy_refs_source_buffer; + ASSERT_EQ(source.size(), copy.size()); +}
\ No newline at end of file diff --git a/base/platform_thread.cc b/base/platform_thread.cc new file mode 100644 index 0000000..1966ab2 --- /dev/null +++ b/base/platform_thread.cc @@ -0,0 +1,51 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/platform_thread.h" + +// static +PlatformThread PlatformThread::Current() { + PlatformThread thread; + +#ifdef WIN32 + thread.thread_ = GetCurrentThread(); +#else + thread.thread_ = pthread_self(); +#endif + + return thread; +} + +bool PlatformThread::operator==(const PlatformThread& other_thread) { +#ifdef WIN32 + return thread_ == other_thread.thread_; +#else + return pthread_equal(thread_, other_thread.thread_); +#endif +} diff --git a/base/platform_thread.h b/base/platform_thread.h new file mode 100644 index 0000000..b6fb45d --- /dev/null +++ b/base/platform_thread.h @@ -0,0 +1,53 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_PLATFORM_THREAD_H__ +#define BASE_PLATFORM_THREAD_H__ + +#ifdef WIN32 +#include <windows.h> +typedef HANDLE PlatformThreadHandle; +#else +#include <pthread.h> +typedef pthread_t PlatformThreadHandle; +#endif + +class PlatformThread { + public: + // Gets the current thread. + static PlatformThread Current(); + + bool operator==(const PlatformThread& other_thread); + + private: + + PlatformThreadHandle thread_; +}; + +#endif // BASE_PLATFORM_THREAD_H__ diff --git a/base/port.h b/base/port.h new file mode 100644 index 0000000..db688dc --- /dev/null +++ b/base/port.h @@ -0,0 +1,57 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_PORT_H__ +#define BASE_PORT_H__ + +#ifdef MSC_VER +#define GG_LONGLONG(x) x##I64 +#define GG_ULONGLONG(x) x##UI64 +#else +#define GG_LONGLONG(x) x##LL +#define GG_ULONGLONG(x) x##ULL +#endif + +// Per C99 7.8.14, define __STDC_CONSTANT_MACROS before including <stdint.h> +// to get the INTn_C and UINTn_C macros for integer constants. It's difficult +// to guarantee any specific ordering of header includes, so it's difficult to +// guarantee that the INTn_C macros can be defined by including <stdint.h> at +// any specific point. Provide GG_INTn_C macros instead. + +#define GG_INT8_C(x) (x) +#define GG_INT16_C(x) (x) +#define GG_INT32_C(x) (x) +#define GG_INT64_C(x) GG_LONGLONG(x) + +#define GG_UINT8_C(x) (x ## U) +#define GG_UINT16_C(x) (x ## U) +#define GG_UINT32_C(x) (x ## U) +#define GG_UINT64_C(x) GG_ULONGLONG(x) + +#endif // BASE_PORT_H__ diff --git a/base/pr_time_test.cc b/base/pr_time_test.cc new file mode 100644 index 0000000..e054164 --- /dev/null +++ b/base/pr_time_test.cc @@ -0,0 +1,179 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/logging.h" +#include "base/third_party/nspr/prtime.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include <time.h> + +namespace { + +const int64 kMicrosecondsPerSecond = 1000000i64; + +// time_t representation of 15th Oct 2007 12:45:00 PDT +PRTime comparison_time_pdt = 1192477500 * kMicrosecondsPerSecond; + +// Specialized test fixture allowing time strings without timezones to be +// tested by comparing them to a known time in the local zone. +class PRTimeTest : public testing::Test { + protected: + virtual void SetUp() { + // Use mktime to get a time_t, and turn it into a PRTime by converting + // seconds to microseconds. Use 15th Oct 2007 12:45:00 local. This + // must be a time guaranteed to be outside of a DST fallback hour in + // any timezone. + struct tm local_comparison_tm = { + 0, // second + 45, // minute + 12, // hour + 15, // day of month + 10 - 1, // month + 2007 - 1900, // year + 0, // day of week (ignored, output only) + 0, // day of year (ignored, output only) + -1 // DST in effect, -1 tells mktime to figure it out + }; + comparison_time_local_ = mktime(&local_comparison_tm) * + kMicrosecondsPerSecond; + ASSERT_GT(comparison_time_local_, 0); + } + + PRTime comparison_time_local_; +}; + +} // namespace + +// Tests the PR_ParseTimeString nspr helper function for +// a variety of time strings. +TEST_F(PRTimeTest, ParseTimeTest1) { + //time_t current_time = 0; + PRTime current_time = 0; + time(¤t_time); + + tm local_time = {0}; + localtime_s(&local_time,¤t_time); + + char time_buf[MAX_PATH] = {0}; + asctime_s(time_buf, arraysize(time_buf), &local_time); + current_time *= PR_USEC_PER_SEC; + + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString(time_buf, PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(current_time,parsed_time); +} + +TEST_F(PRTimeTest, ParseTimeTest2) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Mon, 15 Oct 2007 19:45:00 GMT", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +TEST_F(PRTimeTest, ParseTimeTest3) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("15 Oct 07 12:45:00", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest4) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("15 Oct 07 19:45 GMT", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +TEST_F(PRTimeTest, ParseTimeTest5) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Mon Oct 15 12:45 PDT 2007", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +TEST_F(PRTimeTest, ParseTimeTest6) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Monday, Oct 15, 2007 12:45 PM", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest7) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("10/15/07 12:45:00 PM", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest8) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("15-OCT-2007 12:45pm", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest9) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("16 Oct 2007 4:45-JST (Tuesday)", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +// This tests the Time::FromString wrapper over PR_ParseTimeString +TEST_F(PRTimeTest, ParseTimeTest10) { + Time parsed_time; + bool result = Time::FromString(L"15/10/07 12:45",&parsed_time); + EXPECT_EQ(true, result); + + time_t computed_time = parsed_time.ToTimeT(); + time_t time_to_compare = comparison_time_local_ / kMicrosecondsPerSecond; + EXPECT_EQ(computed_time, time_to_compare); +} + +// This tests the Time::FromString wrapper over PR_ParseTimeString +TEST_F(PRTimeTest, ParseTimeTest11) { + Time parsed_time; + bool result = Time::FromString(L"Mon, 15 Oct 2007 19:45:00 GMT", + &parsed_time); + EXPECT_EQ(true, result); + + time_t computed_time = parsed_time.ToTimeT(); + time_t time_to_compare = comparison_time_pdt / kMicrosecondsPerSecond; + EXPECT_EQ(computed_time, time_to_compare); +} diff --git a/base/process.cc b/base/process.cc new file mode 100644 index 0000000..462c84b --- /dev/null +++ b/base/process.cc @@ -0,0 +1,134 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/process.h" +#include "base/logging.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" + +bool Process::IsProcessBackgrounded() { + DCHECK(process_); + DWORD priority = GetPriorityClass(process_); + if (priority == 0) + return false; // Failure case. + return priority == BELOW_NORMAL_PRIORITY_CLASS; +} + +bool Process::SetProcessBackgrounded(bool value) { + DCHECK(process_); + DWORD priority = value ? BELOW_NORMAL_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS; + return (SetPriorityClass(process_, priority) != 0); +} + +// According to MSDN, these are the default values which XP +// uses to govern working set soft limits. +// http://msdn.microsoft.com/en-us/library/ms686234.aspx +static const int kWinDefaultMinSet = 50 * 4096; +static const int kWinDefaultMaxSet = 345 * 4096; +static const int kDampingFactor = 2; + +bool Process::ReduceWorkingSet() { + if (!process_) + return false; + // The idea here is that when we the process' working set has gone + // down, we want to release those pages to the OS quickly. However, + // when it is not going down, we want to be careful not to release + // too much back to the OS, as it could cause additional paging. + + // We use a damping function to lessen the working set over time. + // As the process grows/shrinks, this algorithm will lag with + // working set reduction. + // + // The intended algorithm is: + // TargetWorkingSetSize = (LastWorkingSet/2 + CurrentWorkingSet) /2 + + scoped_ptr<process_util::ProcessMetrics> metrics( + process_util::ProcessMetrics::CreateProcessMetrics(process_)); + process_util::WorkingSetKBytes working_set; + if (!metrics->GetWorkingSetKBytes(&working_set)) + return false; + + + // We want to compute the amount of working set that the process + // needs to keep in memory. Since other processes contain the + // pages which are shared, we don't need to reserve them in our + // process, the system already has them tagged. Keep in mind, we + // don't get to control *which* pages get released, but if we + // assume reasonable distribution of pages, this should generally + // be the right value. + size_t current_working_set_size = working_set.priv + + working_set.shareable; + + size_t max_size = current_working_set_size; + if (last_working_set_size_) + max_size = (max_size + last_working_set_size_) / 2; // Average. + max_size *= 1024; // Convert to KBytes. + last_working_set_size_ = current_working_set_size / kDampingFactor; + + BOOL rv = SetProcessWorkingSetSize(process_, kWinDefaultMinSet, max_size); + return rv == TRUE; +} + +bool Process::UnReduceWorkingSet() { + if (!process_) + return false; + + if (!last_working_set_size_) + return true; // There was nothing to undo. + + // We've had a reduced working set. Make sure we have lots of + // headroom now that we're active again. + size_t limit = last_working_set_size_ * kDampingFactor * 2 * 1024; + BOOL rv = SetProcessWorkingSetSize(process_, kWinDefaultMinSet, limit); + return rv == TRUE; +} + +bool Process::EmptyWorkingSet() { + if (!process_) + return false; + + BOOL rv = SetProcessWorkingSetSize(process_, -1, -1); + return rv == TRUE; +} + +int32 Process::pid() const { + if (process_ == 0) + return 0; + + return process_util::GetProcId(process_); +} + +bool Process::is_current() const { + return process_ == GetCurrentProcess(); +} + +// static +Process Process::Current() { + return Process(GetCurrentProcess()); +} diff --git a/base/process.h b/base/process.h new file mode 100644 index 0000000..b8ea94f --- /dev/null +++ b/base/process.h @@ -0,0 +1,106 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_PROCESS_H__ +#define BASE_PROCESS_H__ + +#include <windows.h> +#include "base/basictypes.h" + +// ProcessHandle is a platform specific type which represents the underlying OS +// handle to a process. +#ifdef WIN32 +typedef HANDLE ProcessHandle; +#else +typedef int ProcessHandle; +#endif + +// A Process +// TODO(mbelshe): Replace existing code which uses the ProcessHandle w/ the +// Process object where relevant. +class Process { + public: + Process() : process_(0), last_working_set_size_(0) {} + explicit Process(ProcessHandle handle) : + process_(handle), last_working_set_size_(0) {} + + // A handle to the current process. + static Process Current(); + + // Get/Set the handle for this process. + ProcessHandle handle() { return process_; } + void set_handle(ProcessHandle handle) { process_ = handle; } + + // Get the PID for this process. + int32 pid() const; + + // Is the this process the current process. + bool is_current() const; + + // Close the Process Handle. + void Close() { + CloseHandle(process_); + process_ = 0; + } + + // A process is backgrounded when it's priority is lower than normal. + // Return true if this process is backgrounded, false otherwise. + bool IsProcessBackgrounded(); + + // Set a prcess 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); + + // Reduces the working set of memory used by the process. + // The algorithm used by this function is intentionally vague. Repeated calls + // to this function consider the process' previous required Working Set sizes + // to determine a reasonable reduction. This helps give memory back to the OS + // in increments without over releasing memory. + // When the WorkingSet is reduced, it is permanent, until the caller calls + // UnReduceWorkingSet. + // Returns true if successful, false otherwise. + bool ReduceWorkingSet(); + + // Undoes the effects of prior calls to ReduceWorkingSet(). + // Returns true if successful, false otherwise. + bool UnReduceWorkingSet(); + + // Releases as much of the working set back to the OS as possible. + // Returns true if successful, false otherwise. + bool EmptyWorkingSet(); + + private: + ProcessHandle process_; + size_t last_working_set_size_; +}; + +#endif // BASE_PROCESS_H__ diff --git a/base/process_util.cc b/base/process_util.cc new file mode 100644 index 0000000..09631ac --- /dev/null +++ b/base/process_util.cc @@ -0,0 +1,581 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/process_util.h" + +#include <windows.h> +#include <winternl.h> +#include <psapi.h> + +#include "base/logging.h" +#include "base/scoped_ptr.h" + +namespace { + +// System pagesize. This value remains constant on x86/64 architectures. +const int PAGESIZE_KB = 4; + +// HeapSetInformation function pointer. +typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); + +} // namespace + +namespace process_util { + +int GetCurrentProcId() { + return ::GetCurrentProcessId(); +} + +// Helper for GetProcId() +bool GetProcIdViaGetProcessId(ProcessHandle process, DWORD* id) { + // Dynamically get a pointer to GetProcessId(). + typedef DWORD (WINAPI *GetProcessIdFunction)(HANDLE); + static GetProcessIdFunction GetProcessIdPtr = NULL; + static bool initialize_get_process_id = true; + if (initialize_get_process_id) { + initialize_get_process_id = false; + HMODULE kernel32_handle = GetModuleHandle(L"kernel32.dll"); + if (!kernel32_handle) { + NOTREACHED(); + return false; + } + GetProcessIdPtr = reinterpret_cast<GetProcessIdFunction>(GetProcAddress( + kernel32_handle, "GetProcessId")); + } + if (!GetProcessIdPtr) + return false; + // Ask for the process ID. + *id = (*GetProcessIdPtr)(process); + return true; +} + +// Helper for GetProcId() +bool GetProcIdViaNtQueryInformationProcess(ProcessHandle process, DWORD* id) { + // Dynamically get a pointer to NtQueryInformationProcess(). + typedef NTSTATUS (WINAPI *NtQueryInformationProcessFunction)( + HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); + static NtQueryInformationProcessFunction NtQueryInformationProcessPtr = NULL; + static bool initialize_query_information_process = true; + if (initialize_query_information_process) { + initialize_query_information_process = false; + // According to nsylvain, ntdll.dll is guaranteed to be loaded, even though + // the Windows docs seem to imply that you should LoadLibrary() it. + HMODULE ntdll_handle = GetModuleHandle(L"ntdll.dll"); + if (!ntdll_handle) { + NOTREACHED(); + return false; + } + NtQueryInformationProcessPtr = + reinterpret_cast<NtQueryInformationProcessFunction>(GetProcAddress( + ntdll_handle, "NtQueryInformationProcess")); + } + if (!NtQueryInformationProcessPtr) + return false; + // Ask for the process ID. + PROCESS_BASIC_INFORMATION info; + ULONG bytes_returned; + NTSTATUS status = (*NtQueryInformationProcessPtr)(process, + ProcessBasicInformation, + &info, sizeof info, + &bytes_returned); + if (!SUCCEEDED(status) || (bytes_returned != (sizeof info))) + return false; + + *id = static_cast<DWORD>(info.UniqueProcessId); + return true; +} + +int GetProcId(ProcessHandle process) { + // Get a handle to |process| that has PROCESS_QUERY_INFORMATION rights. + HANDLE current_process = GetCurrentProcess(); + HANDLE process_with_query_rights; + if (DuplicateHandle(current_process, process, current_process, + &process_with_query_rights, PROCESS_QUERY_INFORMATION, + false, 0)) { + // Try to use GetProcessId(), if it exists. Fall back on + // NtQueryInformationProcess() otherwise (< Win XP SP1). + DWORD id; + bool success = + GetProcIdViaGetProcessId(process_with_query_rights, &id) || + GetProcIdViaNtQueryInformationProcess(process_with_query_rights, &id); + CloseHandle(process_with_query_rights); + if (success) + return id; + } + + // We're screwed. + NOTREACHED(); + return 0; +} + +bool LaunchApp(const std::wstring& cmdline, + bool wait, bool start_hidden, ProcessHandle* process_handle) { + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + if (start_hidden) { + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.wShowWindow = SW_HIDE; + } + PROCESS_INFORMATION process_info; + if (!CreateProcess(NULL, + const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, + FALSE, 0, NULL, NULL, + &startup_info, &process_info)) + return false; + + // Handles must be closed or they will leak + CloseHandle(process_info.hThread); + + if (wait) + WaitForSingleObject(process_info.hProcess, INFINITE); + + // If the caller wants the process handle, we won't close it. + if (process_handle) { + *process_handle = process_info.hProcess; + } else { + CloseHandle(process_info.hProcess); + } + return true; +} + +// 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 KillProcess(int process_id, int exit_code, bool wait) { + bool result = false; + HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, + FALSE, // Don't inherit handle + process_id); + if (process) { + result = !!TerminateProcess(process, exit_code); + if (result && wait) { + // The process may not end immediately due to pending I/O + if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) + DLOG(ERROR) << "Error waiting for process exit: " << GetLastError(); + } else { + DLOG(ERROR) << "Unable to terminate process: " << GetLastError(); + } + CloseHandle(process); + } + return result; +} + +bool DidProcessCrash(ProcessHandle handle) { + DWORD exitcode = 0; + BOOL success = ::GetExitCodeProcess(handle, &exitcode); + DCHECK(success); + DCHECK(exitcode != STILL_ACTIVE); + + if (exitcode == 0 || // Normal termination. + exitcode == 1 || // Killed by task manager. + exitcode == 0xC0000354 || // STATUS_DEBUGGER_INACTIVE + exitcode == 0xC000013A || // Control-C/end session. + exitcode == 0x40010004) { // Debugger terminated process/end session. + return false; + } + + // All other exit codes indicate crashes. + return true; +} + +NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name, + const ProcessFilter* filter) : + started_iteration_(false), + executable_name_(executable_name), + filter_(filter) { + snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + } + +NamedProcessIterator::~NamedProcessIterator() { + CloseHandle(snapshot_); +} + + +const ProcessEntry* NamedProcessIterator::NextProcessEntry() { + bool result = false; + do { + result = CheckForNextProcess(); + } while (result && !IncludeEntry()); + + if (result) { + return &entry_; + } + + return NULL; +} + +bool NamedProcessIterator::CheckForNextProcess() { + InitProcessEntry(&entry_); + + if (!started_iteration_) { + started_iteration_ = true; + return !!Process32First(snapshot_, &entry_); + } + + return !!Process32Next(snapshot_, &entry_); +} + +bool NamedProcessIterator::IncludeEntry() { + return _wcsicmp(executable_name_.c_str(), entry_.szExeFile) == 0 && + (!filter_ || filter_->Includes(entry_.th32ProcessID, + entry_.th32ParentProcessID)); +} + +void NamedProcessIterator::InitProcessEntry(ProcessEntry* entry) { + memset(entry, 0, sizeof(*entry)); + entry->dwSize = sizeof(*entry); +} + +int GetProcessCount(const std::wstring& executable_name, + const ProcessFilter* filter) { + int count = 0; + + NamedProcessIterator iter(executable_name, filter); + while (iter.NextProcessEntry()) + ++count; + return count; +} + +bool KillProcesses(const std::wstring& executable_name, int exit_code, + const ProcessFilter* filter) { + bool result = true; + const ProcessEntry* entry; + + NamedProcessIterator iter(executable_name, filter); + while (entry = iter.NextProcessEntry()) + result = KillProcess((*entry).th32ProcessID, exit_code, true) && result; + + return result; +} + +bool WaitForProcessesToExit(const std::wstring& executable_name, + int wait_milliseconds, + 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_milliseconds - + static_cast<int>(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 CleanupProcesses(const std::wstring& executable_name, + int wait_milliseconds, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = + process_util::WaitForProcessesToExit(executable_name, wait_milliseconds, + filter); + if (!exited_cleanly) + process_util::KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + + +/////////////////////////////////////////////////////////////////////////////// +// ProcesMetrics + +ProcessMetrics::ProcessMetrics(ProcessHandle process) : process_(process), + last_time_(0), + last_system_time_(0) { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + processor_count_ = system_info.dwNumberOfProcessors; +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +ProcessMetrics::~ProcessMetrics() { } + +size_t ProcessMetrics::GetPagefileUsage() { + 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() { + 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() { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.WorkingSetSize; + } + return 0; +} + +size_t ProcessMetrics::GetPrivateBytes() { + // 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 (GetProcessMemoryInfo(process_, + reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmcx), + sizeof(pmcx))) { + return pmcx.PrivateUsage; + } + return 0; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) { + 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(MEMORY_BASIC_INFORMATION)) == + sizeof(MEMORY_BASIC_INFORMATION)) { + 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(); + } + } + base_address = (static_cast<BYTE*>(mbi.BaseAddress)) + mbi.RegionSize; + } + usage->image = committed_image / 1024; + usage->mapped = committed_mapped / 1024; + usage->priv = committed_private / 1024; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) { + size_t ws_private = 0; + size_t ws_shareable = 0; + size_t ws_shared = 0; + + DCHECK(ws_usage); + memset(ws_usage, 0, sizeof(*ws_usage)); + + DWORD number_of_entries = 4096; // Just a guess. + PSAPI_WORKING_SET_INFORMATION* buffer = NULL; + int retries = 5; + for(;;) { + DWORD buffer_size = sizeof(PSAPI_WORKING_SET_INFORMATION) + + (number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK)); + + // if we can't expand the buffer, don't leak the previous + // contents or pass a NULL pointer to QueryWorkingSet + PSAPI_WORKING_SET_INFORMATION* new_buffer = reinterpret_cast<PSAPI_WORKING_SET_INFORMATION*>( + realloc(buffer, buffer_size)); + if (!new_buffer) { + free(buffer); + return false; + } + buffer = new_buffer; + + // Call the function once to get number of items + if (QueryWorkingSet(process_, buffer, buffer_size)) + break; // Success + + if (GetLastError() != ERROR_BAD_LENGTH) { + free(buffer); + return false; + } + + number_of_entries = static_cast<DWORD>(buffer->NumberOfEntries); + + // Maybe some entries are being added right now. Increase the buffer to + // take that into account. + number_of_entries = static_cast<DWORD>(number_of_entries * 1.25); + + if (--retries == 0) { + free(buffer); // If we're looping, eventually fail. + return false; + } + } + + // On windows 2000 the function returns 1 even when the buffer is too small. + // The number of entries that we are going to parse is the minimum between the + // size we allocated and the real number of entries. + number_of_entries = + std::min(number_of_entries, static_cast<DWORD>(buffer->NumberOfEntries)); + for (unsigned int i = 0; i < number_of_entries; i++) { + if (buffer->WorkingSetInfo[i].Shared) { + ws_shareable++; + if (buffer->WorkingSetInfo[i].ShareCount > 1) + ws_shared++; + } else { + ws_private++; + } + } + + ws_usage->priv = ws_private * PAGESIZE_KB; + ws_usage->shareable = ws_shareable * PAGESIZE_KB; + ws_usage->shared = ws_shared * PAGESIZE_KB; + free(buffer); + return true; +} + +static uint64 FileTimeToUTC(const FILETIME& ftime) { + LARGE_INTEGER li; + li.LowPart = ftime.dwLowDateTime; + li.HighPart = ftime.dwHighDateTime; + return li.QuadPart; +} + +int 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(time_delta != 0); + if (time_delta == 0) + return 0; + + // We add time_delta / 2 so the result is rounded. + int cpu = static_cast<int>((system_time_delta * 100 + time_delta / 2) / + time_delta); + + last_system_time_ = system_time; + last_time_ = time; + + return cpu; +} + +bool ProcessMetrics::GetIOCounters(IO_COUNTERS* io_counters) { + return GetProcessIoCounters(process_, io_counters) != FALSE; +} + +bool ProcessMetrics::CalculateFreeMemory(FreeMBytes* free) { + const SIZE_T kTopAdress = 0x7F000000; + const SIZE_T kMegabyte = 1024 * 1024; + SIZE_T accumulated = 0; + + MEMORY_BASIC_INFORMATION largest = {0}; + UINT_PTR scan = 0; + while (scan < kTopAdress) { + MEMORY_BASIC_INFORMATION info; + if (!::VirtualQueryEx(process_, reinterpret_cast<void*>(scan), + &info, sizeof(info))) + return false; + if (info.State == MEM_FREE) { + accumulated += info.RegionSize; + UINT_PTR end = scan + info.RegionSize; + if (info.RegionSize > (largest.RegionSize)) + largest = info; + } + scan += info.RegionSize; + } + free->largest = largest.RegionSize / kMegabyte; + free->largest_ptr = largest.BaseAddress; + free->total = accumulated / kMegabyte; + return true; +} + +bool EnableLowFragmentationHeap() { + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + HeapSetFn heap_set = reinterpret_cast<HeapSetFn>(GetProcAddress( + kernel32, + "HeapSetInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail. + if (!heap_set) + return true; + + unsigned number_heaps = GetProcessHeaps(0, NULL); + if (!number_heaps) + return false; + + // Gives us some extra space in the array in case a thread is creating heaps + // at the same time we're querying them. + static const int MARGIN = 8; + scoped_array<HANDLE> heaps(new HANDLE[number_heaps + MARGIN]); + number_heaps = GetProcessHeaps(number_heaps + MARGIN, heaps.get()); + if (!number_heaps) + return false; + + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG lfh_flag = 2; + // Don't bother with the result code. It may fails on heaps that have the + // HEAP_NO_SERIALIZE flag. This is expected and not a problem at all. + heap_set(heaps[i], + HeapCompatibilityInformation, + &lfh_flag, + sizeof(lfh_flag)); + } + return true; +} + +} // namespace process_util diff --git a/base/process_util.h b/base/process_util.h new file mode 100644 index 0000000..1bdd9e9 --- /dev/null +++ b/base/process_util.h @@ -0,0 +1,285 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file/namespace contains utility functions for enumerating, ending and +// computing statistics of processes. + +#ifndef BASE_PROCESS_UTIL_H__ +#define BASE_PROCESS_UTIL_H__ + +#include <string> +#ifdef WIN32 +#include <windows.h> +#include <tlhelp32.h> +#endif // WIN32 + +#include "base/basictypes.h" +#include "base/process.h" + +// ProcessHandle is a platform specific type which represents the underlying OS +// handle to a process. +#ifdef WIN32 +typedef PROCESSENTRY32 ProcessEntry; +typedef IO_COUNTERS IoCounters; +#else +typedef int ProcessEntry; +typedef int IoCounters; //TODO(awalker): replace with struct when available +#endif + +namespace process_util { + +// Returns the id of the current process. +int GetCurrentProcId(); + +// 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. +int GetProcId(ProcessHandle process); + +// Runs the given application name with the given command line. Normally, the +// first command line argument should be the path to the process, and don't +// forget to quote it. +// +// If wait is true, it will block and wait for the other process to finish, +// otherwise, it will just continue asynchronously. +// +// Example (including literal quotes) +// cmdline = "c:\windows\explorer.exe" -foo "c:\bar\" +// +// If process_handle is non-NULL, the process handle of the launched app will be +// stored there on a successful launch. +// NOTE: In this case, the caller is responsible for closing the handle so +// that it doesn't leak! +bool LaunchApp(const std::wstring& cmdline, + bool wait, bool start_hidden, ProcessHandle* process_handle); + +// 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(uint32 pid, uint32 parent_pid) const = 0; +}; + +// 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. +int GetProcessCount(const std::wstring& executable_name, + const ProcessFilter* filter); + +// Attempts to kill all the processes on the current machine that were launched +// from the given executable name, ending them with the given exit code. If +// filter is non-null, then only processes selected by the filter are killed. +// Returns false if all processes were able to be killed off, false if at least +// one couldn't be killed. +bool KillProcesses(const std::wstring& 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. +bool KillProcess(int process_id, int exit_code, bool wait); + +// Get the termination status (exit code) of the process and return true if the +// status indicates the process crashed. It is an error to call this if the +// process hasn't terminated yet. +bool DidProcessCrash(ProcessHandle handle); + +// 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. +bool WaitForProcessesToExit(const std::wstring& executable_name, + int wait_milliseconds, + const ProcessFilter* filter); + +// 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. +bool CleanupProcesses(const std::wstring& executable_name, + int wait_milliseconds, + int exit_code, + const ProcessFilter* filter); + +// 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 NamedProcessIterator { + public: + NamedProcessIterator(const std::wstring& executable_name, + const ProcessFilter* filter); + ~NamedProcessIterator(); + + // 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(); + + 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(); + + bool IncludeEntry(); + + // Initializes a PROCESSENTRY32 data structure so that it's ready for + // use with Process32First/Process32Next. + void InitProcessEntry(ProcessEntry* entry); + + std::wstring executable_name_; +#ifdef WIN32 + HANDLE snapshot_; +#endif WIN32 + bool started_iteration_; + ProcessEntry entry_; + const ProcessFilter* filter_; + + DISALLOW_EVIL_CONSTRUCTORS(NamedProcessIterator); +}; + +// Working Set (resident) memory usage broken down by +// 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. +struct WorkingSetKBytes { + size_t priv; + size_t shareable; + size_t shared; +}; + +// Committed (resident + paged) memory usage broken down by +// private: These pages cannot be shared with any other process. +// mapped: These pages are mapped into the view of a section (backed by +// pagefile.sys) +// image: These pages are mapped into the view of an image section (backed by +// file system) +struct CommittedKBytes { + 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; +}; + +// 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 ProcessMetrics { + public: + // Creates a ProcessMetrics for the specified process. + // The caller owns the returned object. + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process); + + ~ProcessMetrics(); + + // Returns the current space allocated for the pagefile, in bytes (these pages + // may or may not be in memory). + size_t GetPagefileUsage(); + // Returns the peak space allocated for the pagefile, in bytes. + size_t GetPeakPagefileUsage(); + // Returns the current working set size, in bytes. + size_t GetWorkingSetSize(); + // Returns private usage, in bytes. Private bytes is the amount + // of memory currently allocated to a process that cannot be shared. + // Note: returns 0 on unsupported OSes: prior to XP SP2. + size_t GetPrivateBytes(); + // Fills a CommittedKBytes with both resident and paged + // memory usage as per definition of CommittedBytes. + void GetCommittedKBytes(CommittedKBytes* usage); + // Fills a WorkingSetKBytes containing resident private and shared memory + // usage in bytes, as per definition of WorkingSetBytes. + bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage); + + // 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); + + // 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. + // Note that on multi-processor machines, 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. + int 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); + + private: + explicit ProcessMetrics(ProcessHandle process); + + ProcessHandle process_; + + int processor_count_; + + // Used to store the previous times so we can compute the CPU usage. + int64 last_time_; + int64 last_system_time_; + + DISALLOW_EVIL_CONSTRUCTORS(ProcessMetrics); +}; + +// 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. +bool EnableLowFragmentationHeap(); + +} // namespace process_util + + +#endif // BASE_PROCESS_UTIL_H__ diff --git a/base/process_util_unittest.cc b/base/process_util_unittest.cc new file mode 100644 index 0000000..d8bfb33 --- /dev/null +++ b/base/process_util_unittest.cc @@ -0,0 +1,99 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#define _CRT_SECURE_NO_WARNINGS + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/process_util.h" + +TEST(ProcessUtilTest, EnableLFH) { + ASSERT_TRUE(process_util::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; + } + 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, HeapQueryInformation(heaps[i], + HeapCompatibilityInformation, + &flag, + sizeof(flag), + &length)); + // If flag is 0, the heap is a standard heap that does not support + // look-asides. If flag is 1, the heap supports look-asides. If flag is 2, + // the heap is a low-fragmentation heap (LFH). Note that look-asides are not + // supported on the LFH. + + // We don't have any documented way of querying the HEAP_NO_SERIALIZE flag. + EXPECT_LE(flag, 2u); + EXPECT_NE(flag, 1u); + } +} + +TEST(ProcessUtilTest, CalcFreeMemory) { + process_util::ProcessMetrics* metrics = + process_util::ProcessMetrics::CreateProcessMetrics(::GetCurrentProcess()); + ASSERT_TRUE(NULL != metrics); + + // Typical values here is ~1900 for total and ~1000 for largest. Obviously + // it depends in what other tests have done to this process. + process_util::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; + char* alloc = new char[kAllocMB * 1024 * 1024]; + EXPECT_TRUE(NULL != alloc); + + size_t expected_total = free_mem1.total - kAllocMB; + size_t expected_largest = free_mem1.largest; + + process_util::FreeMBytes free_mem2 = {0}; + EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem2)); + EXPECT_GE(free_mem2.total, free_mem2.largest); + EXPECT_GE(expected_total, free_mem2.total); + EXPECT_GE(expected_largest, free_mem2.largest); + EXPECT_TRUE(NULL != free_mem2.largest_ptr); + + delete[] alloc; + delete metrics; +} diff --git a/base/ref_counted.h b/base/ref_counted.h new file mode 100644 index 0000000..36f4ab4 --- /dev/null +++ b/base/ref_counted.h @@ -0,0 +1,249 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_REF_COUNTED_H__ +#define BASE_REF_COUNTED_H__ + +#include "base/atomic.h" +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { + +// +// A base class for reference counted classes. Otherwise, known as a cheap +// knock-off of WebKit's RefCounted<T> class. To use this guy just extend your +// class from it like so: +// +// class MyFoo : public base::RefCounted<MyFoo> { +// ... +// }; +// +template <class T> +class RefCounted { + public: + RefCounted() : ref_count_(0) { +#ifndef NDEBUG + in_dtor_ = false; +#endif + } + + ~RefCounted() { +#ifndef NDEBUG + DCHECK(in_dtor_) << "RefCounted object deleted without calling Release()"; +#endif + } + + void AddRef() { +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + ++ref_count_; + } + + void Release() { +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + if (--ref_count_ == 0) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + delete static_cast<T*>(this); + } + } + + private: + int ref_count_; +#ifndef NDEBUG + bool in_dtor_; +#endif + + DISALLOW_EVIL_CONSTRUCTORS(RefCounted<T>); +}; + +// +// A thread-safe variant of RefCounted<T> +// +// class MyFoo : public base::RefCountedThreadSafe<MyFoo> { +// ... +// }; +// +template <class T> +class RefCountedThreadSafe { + public: + RefCountedThreadSafe() : ref_count_(0) { +#ifndef NDEBUG + in_dtor_ = false; +#endif + } + + ~RefCountedThreadSafe() { +#ifndef NDEBUG + DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without " << + "calling Release()"; +#endif + } + + void AddRef() { +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + AtomicIncrement(&ref_count_); + } + + void Release() { +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + // We need to insert memory barriers to ensure that state written before + // the reference count became 0 will be visible to a thread that has just + // made the count 0. + // TODO(wtc): Bug 1112286: use the barrier variant of AtomicDecrement. + if (AtomicDecrement(&ref_count_) == 0) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + delete static_cast<T*>(this); + } + } + + private: + int32 ref_count_; +#ifndef NDEBUG + bool in_dtor_; +#endif + + DISALLOW_EVIL_CONSTRUCTORS(RefCountedThreadSafe<T>); +}; + +} // namespace base + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted<MyFoo> { +// ... +// }; +// +// void some_function() { +// scoped_refptr<MyFoo> foo = new MyFoo(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr<MyFoo> foo = new MyFoo(); +// ... +// foo = NULL; // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr<T> acts like a pointer to T. +// Given two scoped_refptr<T> classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr<MyFoo> a = new MyFoo(); +// scoped_refptr<MyFoo> b; +// +// b.swap(a); +// // now, |b| references the MyFoo object, and |a| references NULL. +// } +// +// To make both |a| and |b| in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// scoped_refptr<MyFoo> a = new MyFoo(); +// scoped_refptr<MyFoo> b; +// +// b = a; +// // now, |a| and |b| each own a reference to the same MyFoo object. +// } +// +template <class T> +class scoped_refptr { + public: + scoped_refptr() : ptr_(NULL) { + } + + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) { + if (ptr_) + ptr_->AddRef(); + } + + ~scoped_refptr() { + if (ptr_) + ptr_->Release(); + } + + T* get() const { return ptr_; } + operator T*() const { return ptr_; } + T* operator->() const { return ptr_; } + + scoped_refptr<T>& operator=(T* p) { + // AddRef first so that self assignment should work + if (p) + p->AddRef(); + if (ptr_ ) + ptr_ ->Release(); + ptr_ = p; + return *this; + } + + scoped_refptr<T>& operator=(const scoped_refptr<T>& r) { + return *this = r.ptr_; + } + + void swap(T** pp) { + T* p = ptr_; + ptr_ = *pp; + *pp = p; + } + + void swap(scoped_refptr<T>& r) { + swap(&r.ptr_); + } + + private: + T* ptr_; +}; + +#endif // BASE_REF_COUNTED_H__ diff --git a/base/ref_counted_unittest.cc b/base/ref_counted_unittest.cc new file mode 100644 index 0000000..ec0186c --- /dev/null +++ b/base/ref_counted_unittest.cc @@ -0,0 +1,41 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/ref_counted.h" + +class SelfAssign : public base::RefCounted<SelfAssign> { +}; + +TEST(RefCountedUnitTest, TestSelfAssignment) { + SelfAssign* p = new SelfAssign; + scoped_refptr<SelfAssign> var = p; + var = var; + EXPECT_EQ(var.get(), p); +} diff --git a/base/registry.cc b/base/registry.cc new file mode 100644 index 0000000..642973f --- /dev/null +++ b/base/registry.cc @@ -0,0 +1,481 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// All Rights Reserved. + +#include <assert.h> +#include <shlwapi.h> +#include <windows.h> + +#include "base/registry.h" + +#pragma comment(lib, "shlwapi.lib") // for SHDeleteKey + +// local types (see the same declarations in the header file) +#define tchar TCHAR +#define CTP const tchar* +#define tstr std::basic_string<tchar> + +// +// RegistryValueIterator +// + + +RegistryValueIterator::RegistryValueIterator(HKEY root_key, + LPCTSTR folder_key) { + 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_); +} + +bool RegistryValueIterator::Valid() const { + // true while the iterator is valid + return key_ != NULL && index_ >= 0; +} + + +void RegistryValueIterator::operator ++ () { + // advance to the next entry in the folder + --index_; + Read(); +} + + +bool RegistryValueIterator::Read() { + if (Valid()) { + DWORD ncount = sizeof(name_)/sizeof(*name_); + value_size_ = sizeof(value_); + LRESULT r = ::RegEnumValue(key_, index_, name_, &ncount, NULL, &type_, + reinterpret_cast<BYTE*>(value_), &value_size_); + if (ERROR_SUCCESS == r) + return true; + } + + name_[0] = '\0'; + value_[0] = '\0'; + value_size_ = 0; + return false; +} + + +DWORD RegistryValueIterator::ValueCount() const { + + DWORD count = 0; + HRESULT result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count, NULL, NULL, NULL, NULL); + + if (result != ERROR_SUCCESS) + return 0; + + return count; +} + + +// +// RegistryKeyIterator +// + + +RegistryKeyIterator::RegistryKeyIterator(HKEY root_key, + LPCTSTR folder_key) { + LONG result = RegOpenKeyEx(root_key, folder_key, 0, KEY_READ, &key_); + if (result != ERROR_SUCCESS) { + key_ = NULL; + } else { + DWORD count = 0; + HRESULT 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_); +} + +bool RegistryKeyIterator::Valid() const { + // true while the iterator is valid + return key_ != NULL && index_ >= 0; +} + + +void RegistryKeyIterator::operator ++ () { + // advance to the next entry in the folder + --index_; + Read(); +} + + +bool RegistryKeyIterator::Read() { + if (Valid()) { + DWORD ncount = sizeof(name_)/sizeof(*name_); + FILETIME written; + LRESULT r = ::RegEnumKeyEx(key_, index_, name_, &ncount, NULL, NULL, + NULL, &written); + if (ERROR_SUCCESS == r) + return true; + } + + name_[0] = '\0'; + return false; +} + + +DWORD RegistryKeyIterator::SubkeyCount() const { + + DWORD count = 0; + HRESULT result = ::RegQueryInfoKey(key_, NULL, 0, NULL, &count, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + + if (result != ERROR_SUCCESS) + return 0; + + return count; +} + + +// +// RegKey +// + + + +RegKey::RegKey(HKEY rootkey, const tchar* subkey, REGSAM access) + : key_(NULL), watch_event_(0) { + if (rootkey) { + if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) + this->Create(rootkey, subkey, access); + else + this->Open(rootkey, subkey, access); + } + else assert(!subkey); +} + + + +void RegKey::Close() { + StopWatching(); + if (key_) { + ::RegCloseKey(key_); + key_ = NULL; + } +} + + + +bool RegKey::Create(HKEY rootkey, const tchar* subkey, REGSAM access) { + DWORD disposition_value; + return CreateWithDisposition(rootkey, subkey, &disposition_value, access); +} + + + +bool RegKey::CreateWithDisposition(HKEY rootkey, const tchar* subkey, + DWORD* disposition, REGSAM access) { + assert(rootkey && subkey && access && disposition); + this->Close(); + + LONG const result = RegCreateKeyEx(rootkey, + subkey, + 0, + NULL, + REG_OPTION_NON_VOLATILE, + access, + NULL, + &key_, + disposition ); + if (result != ERROR_SUCCESS) { + key_ = NULL; + return false; + } + else return true; +} + + + +bool RegKey::Open(HKEY rootkey, const tchar* subkey, REGSAM access) { + assert(rootkey && subkey && access); + this->Close(); + + LONG const result = RegOpenKeyEx(rootkey, subkey, 0, + access, &key_ ); + if (result != ERROR_SUCCESS) { + key_ = NULL; + return false; + } + else return true; +} + + + +bool RegKey::CreateKey(const tchar* name, REGSAM access) { + assert(name && access); + + HKEY subkey = NULL; + LONG const result = RegCreateKeyEx(key_, name, 0, NULL, + REG_OPTION_NON_VOLATILE, + access, NULL, &subkey, NULL); + this->Close(); + + key_ = subkey; + return (result == ERROR_SUCCESS); +} + + + +bool RegKey::OpenKey(const tchar* name, REGSAM access) { + assert(name && access); + + HKEY subkey = NULL; + LONG const result = RegOpenKeyEx(key_, name, 0, access, &subkey); + + this->Close(); + + key_ = subkey; + return (result == ERROR_SUCCESS); +} + + + + +DWORD RegKey::ValueCount() { + DWORD count = 0; + HRESULT const result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, + NULL, &count, NULL, NULL, NULL, NULL); + return (result != ERROR_SUCCESS) ? 0 : count; +} + + +bool RegKey::ReadName(int index, tstr* name) { + tchar buf[256]; + DWORD bufsize = sizeof(buf)/sizeof(*buf); + LRESULT r = ::RegEnumValue(key_, index, buf, &bufsize, NULL, NULL, + NULL, NULL); + if (r != ERROR_SUCCESS) + return false; + if (name) + *name = buf; + return true; +} + + +bool RegKey::ValueExists(const tchar* name) { + if (!key_) return false; + const HRESULT result = RegQueryValueEx(key_, name, 0, NULL, NULL, NULL); + return (result == ERROR_SUCCESS); +} + + + +bool RegKey::ReadValue(const tchar* name, void* data, + DWORD* dsize, DWORD* dtype) { + if (!key_) return false; + HRESULT const result = RegQueryValueEx(key_, name, 0, dtype, + reinterpret_cast<LPBYTE>(data), + dsize); + return (result == ERROR_SUCCESS); +} + + + +bool RegKey::ReadValue(const tchar* name, tstr * value) { + assert(value); + static 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. + TCHAR raw_value[kMaxStringLength]; + DWORD type = REG_SZ, size = sizeof(raw_value); + if (this->ReadValue(name, raw_value, &size, &type)) { + if (type == REG_SZ) { + *value = raw_value; + } else if (type == REG_EXPAND_SZ) { + TCHAR expanded[kMaxStringLength]; + size = ExpandEnvironmentStrings(raw_value, expanded, kMaxStringLength); + // Success: returns the number of TCHARs copied + // Fail: buffer too small, returns the size required + // Fail: other, returns 0 + if (size == 0 || size > kMaxStringLength) + return false; + *value = expanded; + } else { + // Not a string. Oops. + return false; + } + return true; + } + else return false; +} + + + +bool RegKey::ReadValueDW(const tchar* name, DWORD * value) { + assert(value); + DWORD type = REG_DWORD, size = sizeof(DWORD), result = 0; + if (this->ReadValue(name, &result, &size, &type) + && (type == REG_DWORD || type == REG_BINARY) + && size == sizeof(DWORD)) { + *value = result; + return true; + } + else return false; +} + + + +bool RegKey::WriteValue(const tchar* name, const void * data, DWORD dsize, DWORD dtype) { + assert(data); + if (!key_) return false; + HRESULT const result = RegSetValueEx(key_, name, 0, + dtype, + reinterpret_cast<LPBYTE>(const_cast<void*>(data)), + dsize); + return (result == ERROR_SUCCESS); +} + + + +bool RegKey::WriteValue(const tchar * name, const tchar * value) { + return this->WriteValue(name, value, + static_cast<DWORD>(sizeof(*value) * (_tcslen(value) + 1)), REG_SZ); +} + + +bool RegKey::WriteValue(const tchar * name, DWORD value) { + return this->WriteValue(name, &value, + static_cast<DWORD>(sizeof(value)), REG_DWORD); +} + + + +bool RegKey::DeleteKey(const tchar * name) { + if (!key_) return false; + return (ERROR_SUCCESS == SHDeleteKey(key_, name)); +} + + +bool RegKey::DeleteValue(const tchar * value_name) { + assert(value_name); + HRESULT const result = RegDeleteValue(key_, value_name); + return (result == ERROR_SUCCESS); +} + +bool RegKey::StartWatching() { + assert(watch_event_ == 0); + 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. + HRESULT result = RegNotifyChangeKeyValue(key_, TRUE, filter, + watch_event_, TRUE); + if (SUCCEEDED(result)) { + return true; + } else { + CloseHandle(watch_event_); + watch_event_ = 0; + return false; + } +} + +bool RegKey::StopWatching() { + if (watch_event_) { + CloseHandle(watch_event_); + watch_event_ = 0; + return true; + } + return false; +} + +bool RegKey::HasChanged() { + if (watch_event_) { + if (WaitForSingleObject(watch_event_, 0) == WAIT_OBJECT_0) { + // An event only gets signaled once, then it's done, so we have + // to set up another event to watch. + CloseHandle(watch_event_); + watch_event_ = 0; + StartWatching(); + return true; + } + } + return false; +} + + +// Register a COM object with the most usual properties. +bool RegisterCOMServer(const tchar* guid, const tchar* name, const tchar* path) { + RegKey key(HKEY_CLASSES_ROOT, _T("CLSID"), KEY_WRITE); + key.CreateKey(guid, KEY_WRITE); + key.WriteValue(NULL, name); + key.CreateKey(_T("InprocServer32"), KEY_WRITE); + key.WriteValue(NULL, path); + key.WriteValue(_T("ThreadingModel"), _T("Apartment")); + return true; +}; + +bool RegisterCOMServer(const tchar* guid, const tchar* name, HINSTANCE module) { + tchar module_path[MAX_PATH]; + ::GetModuleFileName(module, module_path, MAX_PATH); + _tcslwr_s(module_path, MAX_PATH); + return RegisterCOMServer(guid, name, module_path); +} + +bool UnregisterCOMServer(const tchar* guid) { + RegKey key(HKEY_CLASSES_ROOT, _T("CLSID"), KEY_WRITE); + key.DeleteKey(guid); + return true; +} + +// LocalWords: RegKey diff --git a/base/registry.h b/base/registry.h new file mode 100644 index 0000000..6c70ba7 --- /dev/null +++ b/base/registry.h @@ -0,0 +1,249 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// All Rights Reserved. + +#ifndef BASE_REGISTRY_H__ +#define BASE_REGISTRY_H__ + +#include <windows.h> +#include <tchar.h> +#include <shlwapi.h> +#include <string> + +// The shared file uses a bunch of header files that define types that we don't. +// To avoid changing much code from the standard version, and also to avoid +// polluting our namespace with extra types we don't want, we define these types +// here with the preprocessor and undefine them at the end of the file. +#define tchar TCHAR +#define CTP const tchar* +#define tstr std::basic_string<tchar> + +// RegKey +// Utility class to read from and manipulate the registry. +// Registry vocabulary primer: a "key" is like a folder, in which there +// are "values", which are <name,data> pairs, with an associated data type. + +class RegKey { + public: + RegKey(HKEY rootkey = NULL, CTP subkey = NULL, REGSAM access = KEY_READ); + // start there + + ~RegKey() { this->Close(); } + + bool Create(HKEY rootkey, CTP subkey, REGSAM access = KEY_READ); + + bool CreateWithDisposition(HKEY rootkey, CTP subkey, DWORD* disposition, + REGSAM access = KEY_READ); + + bool Open(HKEY rootkey, CTP subkey, REGSAM access = KEY_READ); + + // Create a subkey (or open if exists) + bool CreateKey(CTP name, REGSAM access); + + // Open a subkey + bool OpenKey(CTP name, REGSAM access); + + // all done, eh? + void Close(); + + DWORD ValueCount(); // Count of the number of value extant + + bool ReadName(int index, tstr* name); // Determine the Nth value's name + + // True while the key is valid + bool Valid() const { return NULL != key_; } + + // Kill key and everything that liveth below it; please be careful out there + bool DeleteKey(CTP name); + + // Delete a single value within the key + bool DeleteValue(CTP name); + + bool ValueExists(CTP name); + bool ReadValue(CTP name, void * data, DWORD * dsize, DWORD * dtype = NULL); + bool ReadValue(CTP name, tstr * value); + bool ReadValueDW(CTP name, DWORD * value); // Named to differ from tstr* + + bool WriteValue(CTP name, const void * data, DWORD dsize, + DWORD dtype = REG_BINARY); + bool WriteValue(CTP name, CTP value); + bool WriteValue(CTP name, DWORD value); + + // StartWatching() + // Start watching the key to see if any of its values have changed. + // The key must have been opened with the KEY_NOTIFY access + // privelege. + bool StartWatching(); + + // HasChanged() + // 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(); + + // StopWatching() + // Will automatically be called by destructor if not manually called + // beforehand. Returns true if it was watching, false otherwise. + bool StopWatching(); + + inline bool IsWatching() const { return watch_event_ != 0; } + HKEY Handle() const { return key_; } + + private: + HKEY key_; // the registry key being iterated + HANDLE watch_event_; +}; + + +// Standalone registry functions -- sorta deprecated, they now map to +// using RegKey + + +// Add a raw data to the registry -- you can pass NULL for the data if +// you just want to create a key +inline bool AddToRegistry(HKEY root_key, CTP key, CTP value_name, + void const * data, DWORD dsize, + DWORD dtype = REG_BINARY) { + return RegKey(root_key, key, KEY_WRITE).WriteValue(value_name, data, dsize, + dtype); +} + +// Convenience routine to add a string value to the registry +inline bool AddToRegistry(HKEY root_key, CTP key, CTP value_name, CTP value) { + return AddToRegistry(root_key, key, value_name, value, + sizeof(*value) * (lstrlen(value) + 1), REG_SZ); +} + +// Read raw data from the registry -- pass something as the dtype +// parameter if you care to learn what type the value was stored as +inline bool ReadFromRegistry(HKEY root_key, CTP key, CTP value_name, + void* data, DWORD* dsize, DWORD* dtype = NULL) { + return RegKey(root_key, key).ReadValue(value_name, data, dsize, dtype); +} + + +// Delete a value or a key from the registry +inline bool DeleteFromRegistry(HKEY root_key, CTP subkey, CTP value_name) { + if (value_name) + return ERROR_SUCCESS == ::SHDeleteValue(root_key, subkey, value_name); + else + return ERROR_SUCCESS == ::SHDeleteKey(root_key, subkey); +} + + + +// delete a key and all subkeys from the registry +inline bool DeleteKeyFromRegistry(HKEY root_key, CTP key_path, CTP key_name) { + RegKey key; + return key.Open(root_key, key_path, KEY_WRITE) + && key.DeleteKey(key_name); +} + + +// Iterates the entries found in a particular folder on the registry. +// For this application I happen to know I wont need data size larger +// than MAX_PATH, but in real life this wouldn't neccessarily be +// adequate. +class RegistryValueIterator { + public: + // Specify a key in construction + RegistryValueIterator(HKEY root_key, LPCTSTR folder_key); + + ~RegistryValueIterator(); + + DWORD ValueCount() const; // count of the number of subkeys extant + + bool Valid() const; // true while the iterator is valid + + void operator++(); // advance to the next entry in the folder + + // The pointers returned by these functions are statics owned by the + // Name and Value functions + CTP Name() const { return name_; } + CTP Value() const { return value_; } + DWORD ValueSize() const { return value_size_; } + DWORD Type() const { return type_; } + + int Index() const { return index_; } + + private: + bool Read(); // read in the current values + + HKEY key_; // the registry key being iterated + int index_; // current index of the iteration + + // Current values + TCHAR name_[MAX_PATH]; + TCHAR value_[MAX_PATH]; + DWORD value_size_; + DWORD type_; +}; + + +class RegistryKeyIterator { + public: + // Specify a parent key in construction + RegistryKeyIterator(HKEY root_key, LPCTSTR folder_key); + + ~RegistryKeyIterator(); + + DWORD SubkeyCount() const; // count of the number of subkeys extant + + bool Valid() const; // true while the iterator is valid + + void operator++(); // advance to the next entry in the folder + + // The pointer returned by Name() is a static owned by the function + CTP Name() const { return name_; } + + int Index() const { return index_; } + + private: + bool Read(); // read in the current values + + HKEY key_; // the registry key being iterated + int index_; // current index of the iteration + + // Current values + TCHAR name_[MAX_PATH]; +}; + + +// Register a COM object with the most usual properties. +bool RegisterCOMServer(const tchar* guid, const tchar* name, + const tchar* modulepath); +bool RegisterCOMServer(const tchar* guid, const tchar* name, HINSTANCE module); +bool UnregisterCOMServer(const tchar* guid); + +// undo the local types defined above +#undef tchar +#undef CTP +#undef tstr + +#endif // BASE_REGISTRY_H__ diff --git a/base/resource_util.cc b/base/resource_util.cc new file mode 100644 index 0000000..e3b5504 --- /dev/null +++ b/base/resource_util.cc @@ -0,0 +1,57 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/resource_util.h" + +#include "base/logging.h" + +namespace base { +bool GetDataResourceFromModule(HMODULE module, int resource_id, + void** data, size_t* length) { + if (!module) + return false; + + // Get a pointer to the data in the dll. + DCHECK(IS_INTRESOURCE(resource_id)); + HRSRC hres_info = FindResource(module, MAKEINTRESOURCE(resource_id), + L"BINDATA"); + if (NULL == hres_info) + return false; + + DWORD data_size = SizeofResource(module, hres_info); + + HGLOBAL hres = LoadResource(module, hres_info); + if (!hres_info) + return false; + + *data = LockResource(hres); + *length = static_cast<size_t>(data_size); + return true; +} +} // namespace diff --git a/base/resource_util.h b/base/resource_util.h new file mode 100644 index 0000000..b592cc2 --- /dev/null +++ b/base/resource_util.h @@ -0,0 +1,49 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file contains utility functions for accessing resources in external +// files (DLLs) or embedded in the executable itself. + +#ifndef BASE_RESOURCE_UTIL_H__ +#define BASE_RESOURCE_UTIL_H__ + +#include <windows.h> +#include <string> + +#include "base/basictypes.h" + +namespace base { +// 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 GetDataResourceFromModule(HMODULE module, int resource_id, + void** data, size_t* length); +} // namespace + +#endif // BASE_RESOURCE_UTIL_H__ diff --git a/base/revocable_store.cc b/base/revocable_store.cc new file mode 100644 index 0000000..2974792 --- /dev/null +++ b/base/revocable_store.cc @@ -0,0 +1,72 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/revocable_store.h" + +#include "base/logging.h" + +RevocableStore::Revocable::Revocable(RevocableStore* store) + : store_reference_(store->owning_reference_) { + // We AddRef() the owning reference. + DCHECK(store_reference_->store()); + store_reference_->store()->Add(this); +} + +RevocableStore::Revocable::~Revocable() { + if (!revoked()) { + // Notify the store of our destruction. + --(store_reference_->store()->count_); + } +} + +RevocableStore::RevocableStore() : count_(0) { + // Create a new owning reference. + owning_reference_ = new StoreRef(this); +} + +RevocableStore::~RevocableStore() { + // Revoke all the items in the store. + owning_reference_->set_store(NULL); +} + +void RevocableStore::Add(Revocable* item) { + DCHECK(!item->revoked()); + ++count_; +} + +void RevocableStore::RevokeAll() { + // We revoke all the existing items in the store and reset our count. + owning_reference_->set_store(NULL); + count_ = 0; + + // Then we create a new owning reference for new items that get added. + // This Release()s the old owning reference, allowing it to be freed after + // all the items that were in the store are eventually destroyed. + owning_reference_ = new StoreRef(this); +} diff --git a/base/revocable_store.h b/base/revocable_store.h new file mode 100644 index 0000000..cee1c64 --- /dev/null +++ b/base/revocable_store.h @@ -0,0 +1,101 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_REVOCABLE_STORE_H__ +#define BASE_REVOCABLE_STORE_H__ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/ref_counted.h" + +// |RevocableStore| is a container of items that can be removed from the store. +class RevocableStore { + public: + // A |StoreRef| is used to link the |RevocableStore| to its items. There is + // one StoreRef per store, and each item holds a reference to it. If the + // store wishes to revoke its items, it sets |store_| to null. Items are + // permitted to release their reference to the |StoreRef| when they no longer + // require the store. + class StoreRef : public base::RefCounted<StoreRef> { + public: + StoreRef(RevocableStore* store) : store_(store) { } + + void set_store(RevocableStore* store) { store_ = store; } + RevocableStore* store() const { return store_; } + + private: + RevocableStore* store_; + + DISALLOW_EVIL_CONSTRUCTORS(StoreRef); + }; + + // An item in the store. On construction, the object adds itself to the + // store. + class Revocable { + public: + Revocable(RevocableStore* store); + ~Revocable(); + + // This item has been revoked if it no longer has a pointer to the store. + bool revoked() const { return !store_reference_->store(); } + + private: + // We hold a reference to the store through this ref pointer. We release + // this reference on destruction. + scoped_refptr<StoreRef> store_reference_; + + DISALLOW_EVIL_CONSTRUCTORS(Revocable); + }; + + RevocableStore(); + ~RevocableStore(); + + // Revokes all the items in the store. + void RevokeAll(); + + // Returns true if there are no items in the store. + bool empty() const { return count_ == 0; } + + private: + friend class Revocable; + + // Adds an item to the store. To add an item to the store, construct it + // with a pointer to the store. + void Add(Revocable* item); + + // This is the reference the unrevoked items in the store hold. + scoped_refptr<StoreRef> owning_reference_; + + // The number of unrevoked items in the store. + int count_; + + DISALLOW_EVIL_CONSTRUCTORS(RevocableStore); +}; + +#endif // BASE_REVOCABLE_STORE_H__ diff --git a/base/run_all_perftests.cc b/base/run_all_perftests.cc new file mode 100644 index 0000000..4ef7494 --- /dev/null +++ b/base/run_all_perftests.cc @@ -0,0 +1,54 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/command_line.h" +#include "base/perftimer.h" +#include "base/string_util.h" +#include "base/test_suite.h" + +int main(int argc, char** argv) { + // Initialize the perf timer log + std::string log_file = + WideToUTF8(CommandLine().GetSwitchValue(L"log-file")); + if (log_file.empty()) + log_file = "perf_test.log"; + ASSERT_TRUE(InitPerfLog(log_file.c_str())); + + // Raise to high priority to have more precise measurements. Since we don't + // aim at 1% precision, it is not necessary to run at realtime level. + if (!IsDebuggerPresent()) + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + + int rv = TestSuite(argc, argv).Run(); + + FinalizePerfLog(); + return rv; +} diff --git a/base/run_all_unittests.cc b/base/run_all_unittests.cc new file mode 100644 index 0000000..69b2d3f --- /dev/null +++ b/base/run_all_unittests.cc @@ -0,0 +1,34 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/test_suite.h" + +int main(int argc, char** argv) { + return TestSuite(argc, argv).Run(); +} diff --git a/base/scoped_cftyperef.h b/base/scoped_cftyperef.h new file mode 100644 index 0000000..f4623fe --- /dev/null +++ b/base/scoped_cftyperef.h @@ -0,0 +1,97 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SCOPED_CFTYPEREF_H__ +#define BASE_SCOPED_CFTYPEREF_H__ + +#include <CoreFoundation/CoreFoundation.h> + +// scoped_cftyperef<> 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. +template<typename CFT> +class scoped_cftyperef { + public: + typedef CFT element_type; + + explicit scoped_cftyperef(CFT object = NULL) + : object_(object) { + } + + ~scoped_cftyperef() { + if (object_) + CFRelease(object_); + } + + void reset(CFT object = NULL) { + if (object_ && object_ != 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(scoped_cftyperef& that) { + CFT temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // scoped_cftyperef<>::release() is like scoped_ptr<>::release. It is NOT + // a wrapper for CFRelease(). To force a scoped_cftyperef<> object to call + // CFRelease(), use scoped_cftyperef<>::reset(). + CFT release() { + CFT temp = object_; + object_ = NULL; + return temp; + } + + private: + CFT object_; + + DISALLOW_EVIL_CONSTRUCTORS(scoped_cftyperef); +}; + +#endif // BASE_SCOPED_CFTYPEREF_H__ diff --git a/base/scoped_handle.h b/base/scoped_handle.h new file mode 100644 index 0000000..63f8d00 --- /dev/null +++ b/base/scoped_handle.h @@ -0,0 +1,213 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SCOPED_HANDLE_H__ +#define BASE_SCOPED_HANDLE_H__ + +#include <windows.h> + +#include "base/basictypes.h" + +// Used so we always remember to close the handle. Example: +// ScopedHandle hfile(CreateFile(...)); +// if (!hfile.Get()) +// ...process error +// ReadFile(hfile.Get(), ...); +// +// To sqirrel the handle away somewhere else: +// secret_handle_ = hfile.Take(); +// +// To explicitly close the handle: +// CloseHandle(hfile.Take()); +class ScopedHandle { + public: + ScopedHandle() : handle_(NULL) { + } + + explicit ScopedHandle(HANDLE h) : handle_(NULL) { + Set(h); + } + + ~ScopedHandle() { + Close(); + } + + // Use this instead of comparing to INVALID_HANDLE_VALUE to pick up our NULL + // usage for errors. + bool IsValid() const { + return handle_ != NULL; + } + + void Set(HANDLE new_handle) { + Close(); + + // Windows is inconsistent about invalid handles, so we always use NULL + if (handle_ != INVALID_HANDLE_VALUE) + handle_ = new_handle; + } + + HANDLE Get() { + return handle_; + } + + operator HANDLE() { return handle_; } + + HANDLE Take() { + // transfers ownership away from this object + HANDLE h = handle_; + handle_ = NULL; + return h; + } + + private: + void Close() { + if (handle_) { + CloseHandle(handle_); + handle_ = NULL; + } + } + + HANDLE handle_; + DISALLOW_EVIL_CONSTRUCTORS(ScopedHandle); +}; + +// Like ScopedHandle, but for HANDLEs returned from FindFile(). +class ScopedFindFileHandle { + public: + explicit ScopedFindFileHandle(HANDLE handle) : handle_(handle) { + // Windows is inconsistent about invalid handles, so we always use NULL + if (handle_ == INVALID_HANDLE_VALUE) + handle_ = NULL; + } + + ~ScopedFindFileHandle() { + if (handle_) + FindClose(handle_); + } + + // Use this instead of comparing to INVALID_HANDLE_VALUE to pick up our NULL + // usage for errors. + bool IsValid() const { return handle_ != NULL; } + + operator HANDLE() { return handle_; } + + private: + HANDLE handle_; + + DISALLOW_EVIL_CONSTRUCTORS(ScopedFindFileHandle); +}; + +// Like ScopedHandle but for HDC. Only use this on HDCs returned from +// CreateCompatibleDC. For an HDC returned by GetDC, use ReleaseDC instead. +class ScopedHDC { + public: + explicit ScopedHDC(HDC h) : hdc_(h) { } + + ~ScopedHDC() { + if (hdc_) + DeleteDC(hdc_); + } + + operator HDC() { return hdc_; } + + private: + HDC hdc_; + DISALLOW_EVIL_CONSTRUCTORS(ScopedHDC); +}; + +// Like ScopedHandle but for HBITMAP. +class ScopedBitmap { + public: + explicit ScopedBitmap(HBITMAP h) : hbitmap_(h) { } + + ~ScopedBitmap() { + if (hbitmap_) + DeleteObject(hbitmap_); + } + + operator HBITMAP() { return hbitmap_; } + + private: + HBITMAP hbitmap_; + DISALLOW_EVIL_CONSTRUCTORS(ScopedBitmap); +}; + +// Like ScopedHandle but for HRGN. +class ScopedHRGN { + public: + explicit ScopedHRGN(HRGN h) : hrgn_(h) { } + + ~ScopedHRGN() { + if (hrgn_) + DeleteObject(hrgn_); + } + + operator HRGN() { return hrgn_; } + + ScopedHRGN& operator=(HRGN hrgn) { + if (hrgn_ && hrgn != hrgn_) + DeleteObject(hrgn_); + hrgn_ = hrgn; + return *this; + } + + private: + HRGN hrgn_; + DISALLOW_EVIL_CONSTRUCTORS(ScopedHRGN); +}; + +// Like ScopedHandle except for HGLOBAL. +template<class T> +class ScopedHGlobal { + public: + explicit ScopedHGlobal(HGLOBAL glob) : glob_(glob) { + data_ = static_cast<T*>(GlobalLock(glob_)); + } + ~ScopedHGlobal() { + GlobalUnlock(glob_); + } + + T* get() { return data_; } + + size_t Size() const { return GlobalSize(glob_); } + + T* operator->() const { + assert(data_ != 0); + return data_; + } + + private: + HGLOBAL glob_; + + T* data_; + + DISALLOW_EVIL_CONSTRUCTORS(ScopedHGlobal); +}; + +#endif // BASE_SCOPED_HANDLE_H__ diff --git a/base/scoped_ptr.h b/base/scoped_ptr.h new file mode 100644 index 0000000..0c9ea95 --- /dev/null +++ b/base/scoped_ptr.h @@ -0,0 +1,402 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// All Rights Reserved. + +#ifndef BASE_SCOPED_PTR_H__ +#define BASE_SCOPED_PTR_H__ + +// This is an implementation designed to match the anticipated future TR2 +// implementation of the scoped_ptr class, and its closely-related brethren, +// scoped_array, scoped_ptr_malloc, and make_scoped_ptr. +// +// See http://wiki/Main/ScopedPointerInterface for the spec that drove this +// file. + +#include <assert.h> +#include <stdlib.h> +#include <cstddef> + + +template <class C> class scoped_ptr; +template <class C, class Free> class scoped_ptr_malloc; +template <class C> class scoped_array; + +template <class C> +scoped_ptr<C> make_scoped_ptr(C *); + +// A scoped_ptr<T> is like a T*, except that the destructor of scoped_ptr<T> +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr<T> owns the T object that it points to. +// Like a T*, a scoped_ptr<T> may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr<T> is thread-compatible, and once you +// dereference it, you get the threadsafety guarantees of T. +// +// The size of a scoped_ptr is small: +// sizeof(scoped_ptr<C>) == sizeof(C*) +template <class C> +class scoped_ptr { + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to intializing with NULL. + // There is no way to create an uninitialized scoped_ptr. + // The input parameter must be allocated with new. + explicit scoped_ptr(C* p = NULL) : ptr_(p) { } + + // Destructor. If there is a C object, delete it. + // We don't need to test ptr_ == NULL because C++ does that for us. + ~scoped_ptr() { + enum { type_must_be_complete = sizeof(C) }; + delete ptr_; + } + + // Reset. Deletes the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (p != ptr_) { + enum { type_must_be_complete = sizeof(C) }; + delete ptr_; + ptr_ = p; + } + } + + // Accessors to get the owned object. + // operator* and operator-> will assert() if there is no current object. + C& operator*() const { + assert(ptr_ != NULL); + return *ptr_; + } + C* operator->() const { + assert(ptr_ != NULL); + return ptr_; + } + C* get() const { return ptr_; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(C* p) const { return ptr_ == p; } + bool operator!=(C* p) const { return ptr_ != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + C* tmp = ptr_; + ptr_ = p2.ptr_; + p2.ptr_ = tmp; + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() { + C* retVal = ptr_; + ptr_ = NULL; + return retVal; + } + + private: + C* ptr_; + + friend scoped_ptr<C> make_scoped_ptr<C>(C *p); + + // Forbid comparison of scoped_ptr types. If C2 != C, it totally doesn't + // make sense, and if C2 == C, it still doesn't make sense because you should + // never have the same object owned by two different scoped_ptrs. + template <class C2> bool operator==(scoped_ptr<C2> const& p2) const; + template <class C2> bool operator!=(scoped_ptr<C2> const& p2) const; + + // Disallow evil constructors + scoped_ptr(const scoped_ptr&); + void operator=(const scoped_ptr&); +}; + +// Free functions +template <class C> +void swap(scoped_ptr<C>& p1, scoped_ptr<C>& p2) { + p1.swap(p2); +} + +template <class C> +bool operator==(C* p1, const scoped_ptr<C>& p2) { + return p1 == p2.get(); +} + +template <class C> +bool operator!=(C* p1, const scoped_ptr<C>& p2) { + return p1 != p2.get(); +} + +template <class C> +scoped_ptr<C> make_scoped_ptr(C *p) { + // This does nothing but to return a scoped_ptr of the type that the passed + // pointer is of. (This eliminates the need to specify the name of T when + // making a scoped_ptr that is used anonymously/temporarily.) From an + // access control point of view, we construct an unnamed scoped_ptr here + // which we return and thus copy-construct. Hence, we need to have access + // to scoped_ptr::scoped_ptr(scoped_ptr const &). However, it is guaranteed + // that we never actually call the copy constructor, which is a good thing + // as we would call the temporary's object destructor (and thus delete p) + // if we actually did copy some object, here. + return scoped_ptr<C>(p); +} + +// scoped_array<C> is like scoped_ptr<C>, except that the caller must allocate +// with new [] and the destructor deletes objects with delete []. +// +// As with scoped_ptr<C>, a scoped_array<C> either points to an object +// or is NULL. A scoped_array<C> owns the object that it points to. +// scoped_array<T> is thread-compatible, and once you index into it, +// the returned objects have only the threadsafety guarantees of T. +// +// Size: sizeof(scoped_array<C>) == sizeof(C*) +template <class C> +class scoped_array { + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to intializing with NULL. + // There is no way to create an uninitialized scoped_array. + // The input parameter must be allocated with new []. + explicit scoped_array(C* p = NULL) : array_(p) { } + + // Destructor. If there is a C object, delete it. + // We don't need to test ptr_ == NULL because C++ does that for us. + ~scoped_array() { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + } + + // Reset. Deletes the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (p != array_) { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + array_ = p; + } + } + + // Get one element of the current object. + // Will assert() if there is no current object, or index i is negative. + C& operator[](std::ptrdiff_t i) const { + assert(i >= 0); + assert(array_ != NULL); + return array_[i]; + } + + // Get a pointer to the zeroth element of the current object. + // If there is no current object, return NULL. + C* get() const { + return array_; + } + + // Comparison operators. + // These return whether two scoped_array refer to the same object, not just to + // two different but equal objects. + bool operator==(C* p) const { return array_ == p; } + bool operator!=(C* p) const { return array_ != p; } + + // Swap two scoped arrays. + void swap(scoped_array& p2) { + C* tmp = array_; + array_ = p2.array_; + p2.array_ = tmp; + } + + // Release an array. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() { + C* retVal = array_; + array_ = NULL; + return retVal; + } + + private: + C* array_; + + // Forbid comparison of different scoped_array types. + template <class C2> bool operator==(scoped_array<C2> const& p2) const; + template <class C2> bool operator!=(scoped_array<C2> const& p2) const; + + // Disallow evil constructors + scoped_array(const scoped_array&); + void operator=(const scoped_array&); +}; + +// Free functions +template <class C> +void swap(scoped_array<C>& p1, scoped_array<C>& p2) { + p1.swap(p2); +} + +template <class C> +bool operator==(C* p1, const scoped_array<C>& p2) { + return p1 == p2.get(); +} + +template <class C> +bool operator!=(C* p1, const scoped_array<C>& p2) { + return p1 != p2.get(); +} + +// This class wraps the c library function free() in a class that can be +// passed as a template argument to scoped_ptr_malloc below. +class ScopedPtrMallocFree { + public: + inline void operator()(void* x) const { + free(x); + } +}; + +// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a +// second template argument, the functor used to free the object. + +template<class C, class FreeProc = ScopedPtrMallocFree> +class scoped_ptr_malloc { + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to intializing 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) {} + + // Destructor. If there is a C object, call the Free functor. + ~scoped_ptr_malloc() { + free_(ptr_); + } + + // 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) { + free_(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_; + } + + // 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 wwith 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() { + C* tmp = ptr_; + ptr_ = NULL; + return tmp; + } + + private: + C* ptr_; + + // no reason to use these: each scoped_ptr_malloc should have its own object + template <class C2, class GP> + bool operator==(scoped_ptr_malloc<C2, GP> const& p) const; + template <class C2, class GP> + bool operator!=(scoped_ptr_malloc<C2, GP> const& p) const; + + static FreeProc const free_; + + // Disallow evil constructors + scoped_ptr_malloc(const scoped_ptr_malloc&); + void operator=(const scoped_ptr_malloc&); +}; + +template<class C, class FP> +FP const scoped_ptr_malloc<C, FP>::free_ = FP(); + +template<class C, class FP> inline +void swap(scoped_ptr_malloc<C, FP>& a, scoped_ptr_malloc<C, FP>& b) { + a.swap(b); +} + +template<class C, class FP> inline +bool operator==(C* p, const scoped_ptr_malloc<C, FP>& b) { + return p == b.get(); +} + +template<class C, class FP> inline +bool operator!=(C* p, const scoped_ptr_malloc<C, FP>& b) { + return p != b.get(); +} + +#endif // BASE_SCOPED_PTR_H__ diff --git a/base/sha2.cc b/base/sha2.cc new file mode 100644 index 0000000..4737a7c --- /dev/null +++ b/base/sha2.cc @@ -0,0 +1,47 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/sha2.h" + +#include "base/third_party/nss/blapi.h" +#include "base/third_party/nss/sha256.h" + +namespace base { + +void SHA256HashString(const std::string& str, void* output, size_t len) { + SHA256Context ctx; + + SHA256_Begin(&ctx); + SHA256_Update(&ctx, reinterpret_cast<const unsigned char*>(str.data()), + static_cast<unsigned int>(str.length())); + SHA256_End(&ctx, static_cast<unsigned char*>(output), NULL, + static_cast<unsigned int>(len)); +} + +} // namespace base diff --git a/base/sha2.h b/base/sha2.h new file mode 100644 index 0000000..b9d5fb2 --- /dev/null +++ b/base/sha2.h @@ -0,0 +1,52 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SHA2_H__ +#define BASE_SHA2_H__ + +#include <string> + +namespace base { + +// These functions perform SHA-256 operations. +// +// Functions for SHA-384 and SHA-512 can be added when the need arises. + +enum { + SHA256_LENGTH = 32 // length in bytes of a SHA-256 hash +}; + +// Computes the SHA-256 hash of the input string 'str' and stores the first +// 'len' bytes of the hash in the output buffer 'output'. If 'len' > 32, +// only 32 bytes (the full hash) are stored in the 'output' buffer. +void SHA256HashString(const std::string& str, void* output, size_t len); + +} // namespace base + +#endif // BASE_SHA2_H__ diff --git a/base/sha2_unittest.cc b/base/sha2_unittest.cc new file mode 100644 index 0000000..c0d6343 --- /dev/null +++ b/base/sha2_unittest.cc @@ -0,0 +1,103 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/sha2.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(Sha256Test, Test1) { + // Example B.1 from FIPS 180-2: one-block message. + std::string input1 = "abc"; + int expected1[] = { 0xba, 0x78, 0x16, 0xbf, + 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, + 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, + 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, + 0xf2, 0x00, 0x15, 0xad }; + + uint8 output1[base::SHA256_LENGTH]; + base::SHA256HashString(input1, output1, sizeof(output1)); + for (int i = 0; i < base::SHA256_LENGTH; i++) + EXPECT_EQ(expected1[i], static_cast<int>(output1[i])); + + uint8 output_truncated1[4]; // 4 bytes == 32 bits + base::SHA256HashString(input1, output_truncated1, sizeof(output_truncated1)); + for (int i = 0; i < sizeof(output_truncated1); i++) + EXPECT_EQ(expected1[i], static_cast<int>(output_truncated1[i])); +} + +TEST(Sha256Test, Test2) { + // Example B.2 from FIPS 180-2: multi-block message. + std::string input2 = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + int expected2[] = { 0x24, 0x8d, 0x6a, 0x61, + 0xd2, 0x06, 0x38, 0xb8, + 0xe5, 0xc0, 0x26, 0x93, + 0x0c, 0x3e, 0x60, 0x39, + 0xa3, 0x3c, 0xe4, 0x59, + 0x64, 0xff, 0x21, 0x67, + 0xf6, 0xec, 0xed, 0xd4, + 0x19, 0xdb, 0x06, 0xc1 }; + + uint8 output2[base::SHA256_LENGTH]; + base::SHA256HashString(input2, output2, sizeof(output2)); + for (int i = 0; i < base::SHA256_LENGTH; i++) + EXPECT_EQ(expected2[i], static_cast<int>(output2[i])); + + uint8 output_truncated2[6]; + base::SHA256HashString(input2, output_truncated2, sizeof(output_truncated2)); + for (int i = 0; i < sizeof(output_truncated2); i++) + EXPECT_EQ(expected2[i], static_cast<int>(output_truncated2[i])); +} + +TEST(Sha256Test, Test3) { + // Example B.3 from FIPS 180-2: long message. + std::string input3(1000000, 'a'); // 'a' repeated a million times + int expected3[] = { 0xcd, 0xc7, 0x6e, 0x5c, + 0x99, 0x14, 0xfb, 0x92, + 0x81, 0xa1, 0xc7, 0xe2, + 0x84, 0xd7, 0x3e, 0x67, + 0xf1, 0x80, 0x9a, 0x48, + 0xa4, 0x97, 0x20, 0x0e, + 0x04, 0x6d, 0x39, 0xcc, + 0xc7, 0x11, 0x2c, 0xd0 }; + + uint8 output3[base::SHA256_LENGTH]; + base::SHA256HashString(input3, output3, sizeof(output3)); + for (int i = 0; i < base::SHA256_LENGTH; i++) + EXPECT_EQ(expected3[i], static_cast<int>(output3[i])); + + uint8 output_truncated3[12]; + base::SHA256HashString(input3, output_truncated3, sizeof(output_truncated3)); + for (int i = 0; i < sizeof(output_truncated3); i++) + EXPECT_EQ(expected3[i], static_cast<int>(output_truncated3[i])); +} diff --git a/base/shared_event.cc b/base/shared_event.cc new file mode 100644 index 0000000..e0870cf --- /dev/null +++ b/base/shared_event.cc @@ -0,0 +1,104 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/shared_event.h" +#include "base/logging.h" +#include "base/time.h" + +SharedEvent::~SharedEvent() { + Close(); +} + +bool SharedEvent::Create(bool manual_reset, bool initial_state) { + DCHECK(!event_handle_); + event_handle_ = CreateEvent(NULL /* security attributes */, manual_reset, + initial_state, NULL /* name */); + DCHECK(event_handle_); + return !!event_handle_; +} + +void SharedEvent::Close() { + if (event_handle_) { + BOOL rv = CloseHandle(event_handle_); + DCHECK(rv); + event_handle_ = NULL; + } +} + +bool SharedEvent::SetSignaledState(bool signaled) { + DCHECK(event_handle_); + BOOL rv; + if (signaled) { + rv = SetEvent(event_handle_); + } else { + rv = ResetEvent(event_handle_); + } + return rv ? true : false; +} + +bool SharedEvent::IsSignaled() { + DCHECK(event_handle_); + DWORD event_state = ::WaitForSingleObject(event_handle_, 0); + DCHECK(WAIT_OBJECT_0 == event_state || WAIT_TIMEOUT == event_state); + return event_state == WAIT_OBJECT_0; +} + +bool SharedEvent::WaitUntilSignaled(const TimeDelta& timeout) { + DCHECK(event_handle_); + DWORD event_state = ::WaitForSingleObject(event_handle_, + static_cast<DWORD>(timeout.InMillisecondsF())); + return event_state == WAIT_OBJECT_0; +} + +bool SharedEvent::WaitForeverUntilSignaled() { + DCHECK(event_handle_); + DWORD event_state = ::WaitForSingleObject(event_handle_, + INFINITE); + return event_state == WAIT_OBJECT_0; +} + +bool SharedEvent::ShareToProcess(ProcessHandle process, + SharedEventHandle *new_handle) { + DCHECK(event_handle_); + HANDLE event_handle_copy; + BOOL rv = DuplicateHandle(GetCurrentProcess(), event_handle_, process, + &event_handle_copy, 0, FALSE, DUPLICATE_SAME_ACCESS); + + if (rv) + *new_handle = event_handle_copy; + return rv ? true : false; +} + +bool SharedEvent::GiveToProcess(ProcessHandle process, + SharedEventHandle *new_handle) { + bool rv = ShareToProcess(process, new_handle); + if (rv) + Close(); + return rv; +} diff --git a/base/shared_event.h b/base/shared_event.h new file mode 100644 index 0000000..2ddebc0 --- /dev/null +++ b/base/shared_event.h @@ -0,0 +1,87 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SHARED_EVENT__ +#define BASE_SHARED_EVENT__ + +#include "base/process_util.h" + +class TimeDelta; + +typedef HANDLE SharedEventHandle; + +class SharedEvent { + public: + // Create a new SharedEvent. + SharedEvent() : event_handle_(NULL) { } + + // Create a SharedEvent from an existing SharedEventHandle. The new + // SharedEvent now owns the SharedEventHandle and will close it. + SharedEvent(SharedEventHandle event_handle) : event_handle_(event_handle) { } + ~SharedEvent(); + + // Create the SharedEvent. + bool Create(bool manual_reset, bool initial_state); + + // Close the SharedEvent. + void Close(); + + // If |signaled| is true, set the signaled state, otherwise, set to nonsignaled. + // Returns false if we can't set the signaled state. + bool SetSignaledState(bool signaled); + + // Returns true if the SharedEvent is signaled. + bool IsSignaled(); + + // Blocks until the event is signaled with a maximum wait time of |timeout|. + // Returns true if the object is signaled within the timeout. + bool WaitUntilSignaled(const TimeDelta& timeout); + + // Blocks until the event is signaled. Returns true if the object is + // signaled, otherwise an error occurred. + bool WaitForeverUntilSignaled(); + + // Get access to the underlying OS handle for this event. + SharedEventHandle handle() { return event_handle_; } + + // Share this SharedEvent with |process|. |new_handle| is an output + // parameter to receive the handle for use in |process|. Returns false if we + // are unable to share the SharedEvent. + bool ShareToProcess(ProcessHandle process, SharedEventHandle *new_handle); + + // The same as ShareToProcess followed by closing the event. + bool GiveToProcess(ProcessHandle process, SharedEventHandle *new_handle); + + private: + SharedEventHandle event_handle_; + + DISALLOW_EVIL_CONSTRUCTORS(SharedEvent); +}; + +#endif // BASE_SHARED_EVENT__ diff --git a/base/shared_event_unittest.cc b/base/shared_event_unittest.cc new file mode 100644 index 0000000..5a0c468 --- /dev/null +++ b/base/shared_event_unittest.cc @@ -0,0 +1,81 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <process.h> // _beginthreadex + +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/shared_event.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SharedEventTest : public testing::Test { +}; + +Lock lock; + +unsigned __stdcall MultipleThreadMain(void* param) { + SharedEvent* shared_event = reinterpret_cast<SharedEvent*>(param); + AutoLock l(lock); + shared_event->SetSignaledState(!shared_event->IsSignaled()); + return 0; +} + +} + +TEST(SharedEventTest, ThreadSignaling) { + // Create a set of 5 threads to each open a shared event and flip the + // signaled state. Verify that when the threads complete, the final state is + // not-signaled. + // I admit this doesn't test much, but short of spawning separate processes + // and using IPC with a SharedEventHandle, there's not much to unittest. + const int kNumThreads = 5; + HANDLE threads[kNumThreads]; + + scoped_ptr<SharedEvent> shared_event(new SharedEvent); + shared_event->Create(true, true); + + // Spawn the threads. + for (int16 index = 0; index < kNumThreads; index++) { + void *argument = reinterpret_cast<void*>(shared_event.get()); + unsigned thread_id; + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, MultipleThreadMain, argument, 0, &thread_id)); + EXPECT_NE(threads[index], static_cast<HANDLE>(NULL)); + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], 60*1000); + EXPECT_EQ(rv, WAIT_OBJECT_0); // verify all threads finished + CloseHandle(threads[index]); + } + EXPECT_FALSE(shared_event->IsSignaled()); +} diff --git a/base/shared_memory.cc b/base/shared_memory.cc new file mode 100644 index 0000000..bd5023f --- /dev/null +++ b/base/shared_memory.cc @@ -0,0 +1,187 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/shared_memory.h" + +#include "base/logging.h" +#include "base/win_util.h" + +SharedMemory::SharedMemory() + : mapped_file_(NULL), + memory_(NULL), + read_only_(false), + max_size_(0), + lock_(NULL) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : mapped_file_(handle), + memory_(NULL), + read_only_(read_only), + max_size_(0), + lock_(NULL) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process) + : mapped_file_(NULL), + memory_(NULL), + read_only_(read_only), + max_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_); +} + +bool SharedMemory::Create(const std::wstring &name, bool read_only, + bool open_existing, size_t size) { + DCHECK(mapped_file_ == NULL); + + name_ = name; + read_only_ = read_only; + mapped_file_ = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, + read_only_ ? PAGE_READONLY : PAGE_READWRITE, 0, static_cast<DWORD>(size), + name.empty() ? NULL : name.c_str()); + if (!mapped_file_) + return false; + + // Check if the shared memory pre-exists. + if (GetLastError() == ERROR_ALREADY_EXISTS && !open_existing) { + Close(); + return false; + } + max_size_ = size; + return true; +} + +bool SharedMemory::Open(const std::wstring &name, bool read_only) { + DCHECK(mapped_file_ == NULL); + + name_ = name; + read_only_ = read_only; + mapped_file_ = OpenFileMapping( + read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, false, + name.empty() ? NULL : name.c_str()); + if (mapped_file_ != NULL) { + // Note: size_ is not set in this case. + return true; + } + return false; +} + +bool SharedMemory::Map(size_t bytes) { + if (mapped_file_ == NULL) + return false; + + memory_ = MapViewOfFile(mapped_file_, + read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, 0, 0, bytes); + if (memory_ != NULL) { + 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() { + if (lock_ == NULL) { + std::wstring name = name_; + name.append(L"lock"); + lock_ = CreateMutex(NULL, FALSE, name.c_str()); + DCHECK(lock_ != NULL); + if (lock_ == NULL) { + DLOG(ERROR) << "Could not create mutex" << GetLastError(); + return; // there is nothing good we can do here. + } + } + WaitForSingleObject(lock_, INFINITE); +} + +void SharedMemory::Unlock() { + DCHECK(lock_ != NULL); + ReleaseMutex(lock_); +} diff --git a/base/shared_memory.h b/base/shared_memory.h new file mode 100644 index 0000000..00b51f9 --- /dev/null +++ b/base/shared_memory.h @@ -0,0 +1,169 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SHARED_MEMORY_H__ +#define BASE_SHARED_MEMORY_H__ + +#include "base/process_util.h" + +// SharedMemoryHandle is a platform specific type which represents +// the underlying OS handle to a shared memory segment. +#ifdef WIN32 +typedef HANDLE SharedMemoryHandle; +typedef HANDLE SharedMemoryLock; +#else +typedef int SharedMemoryHandle; +typedef int SharedMemoryLock; +#endif + +// Platform abstraction for shared memory. Provides a C++ wrapper +// around the OS primitive for a memory mapped file. +class SharedMemory { + public: + // Create a new SharedMemory object. + SharedMemory(); + + // 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); + + // Destructor. Will close any open files. + ~SharedMemory(); + + // Creates or opens a shared memory segment based on a name. + // If read_only is true, opens the memory as read-only. + // If open_existing is true, and the shared memory already exists, + // opens the existing shared memory and ignores the size parameter. + // Returns true on success, false on failure. + bool Create(const std::wstring &name, bool read_only, bool open_existing, + size_t size); + + // 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::wstring &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. + bool Map(size_t bytes); + + // Unmaps the shared memory from the caller's address space. + // Returns true if successful; returns false on error or if the + // memory is not mapped. + bool Unmap(); + + // Get the size of the opened shared memory backing file. + // Note: This size is only available to the creator of the + // shared memory, and not to those that opened shared memory + // created externally. + // Returns 0 if not opened or unknown. + size_t max_size() const { return max_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_; } + + // Get access to the underlying OS handle for this segment. + // Use of this handle for anything other than an opaque + // identifier is not portable. + SharedMemoryHandle handle() const { return mapped_file_; } + + // Closes the open shared memory segment. + // It is safe to call Close repeatedly. + void Close(); + + // Share 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; + bool GiveToProcess(ProcessHandle process, + SharedMemoryHandle *new_handle) { + return ShareToProcessCommon(process, new_handle, true); + } + + // Lock the shared memory. + // This is a cross-process lock which may be recursively + // locked by the same thread. + void Lock(); + + // Release the shared memory lock. + void Unlock(); + + private: + bool ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, bool close_self); + + std::wstring name_; + SharedMemoryHandle mapped_file_; + void* memory_; + bool read_only_; + size_t max_size_; + SharedMemoryLock lock_; + + DISALLOW_EVIL_CONSTRUCTORS(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_EVIL_CONSTRUCTORS(SharedMemoryAutoLock); +}; + + +#endif // BASE_SHARED_MEMORY_H__ diff --git a/base/shared_memory_unittest.cc b/base/shared_memory_unittest.cc new file mode 100644 index 0000000..928326a --- /dev/null +++ b/base/shared_memory_unittest.cc @@ -0,0 +1,180 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <process.h> // _beginthreadex +#include "base/shared_memory.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace { + +class SharedMemoryTest : public testing::Test { +}; + +unsigned __stdcall MultipleThreadMain(void* param) { + // 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. + const int kDataSize = 1024; + std::wstring test_name = L"SharedMemoryOpenThreadTest"; + int16 id = reinterpret_cast<int16>(param); + SharedMemory memory; + bool rv = memory.Create(test_name, false, true, kDataSize); + EXPECT_TRUE(rv); + rv = memory.Map(kDataSize); + EXPECT_TRUE(rv); + int *ptr = static_cast<int*>(memory.memory()) + id; + EXPECT_EQ(*ptr, 0); + for (int idx = 0; idx < 100; idx++) { + *ptr = idx; + Sleep(1); // short wait + EXPECT_EQ(*ptr, idx); + } + memory.Close(); + return 0; +} + +unsigned __stdcall MultipleLockThread(void* param) { + // 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. + const int kDataSize = sizeof(int); + int id = static_cast<int>(reinterpret_cast<INT_PTR>(param)); + SharedMemoryHandle handle = NULL; + { + SharedMemory memory1; + EXPECT_TRUE(memory1.Create(L"SharedMemoryMultipleLockThreadTest", false, true, + kDataSize)); + EXPECT_TRUE(memory1.ShareToProcess(GetCurrentProcess(), &handle)); + } + SharedMemory memory2(handle, false); + EXPECT_TRUE(memory2.Map(kDataSize)); + volatile int* const ptr = static_cast<int*>(memory2.memory()); + for (int idx = 0; idx < 20; idx++) { + memory2.Lock(); + int i = (id << 16) + idx; + *ptr = i; + // short wait + Sleep(1); + EXPECT_EQ(*ptr, i); + memory2.Unlock(); + } + memory2.Close(); + return 0; +} + +} // namespace + +TEST(SharedMemoryTest, OpenClose) { + const int kDataSize = 1024; + std::wstring test_name = L"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.Open(test_name, false); + EXPECT_FALSE(rv); + rv = memory1.Create(test_name, false, 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 + + + // 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 still has the right data. + memory1.Close(); + char *start_ptr = static_cast<char *>(memory2.memory()); + char *end_ptr = start_ptr + kDataSize; + for (char* ptr = start_ptr; ptr < end_ptr; ptr++) + EXPECT_EQ(*ptr, '1'); + + // Close the second memory segment + memory2.Close(); +} + + +TEST(SharedMemoryTest, MultipleThreads) { + // Create a set of 5 threads to each open a shared memory segment + // and write to it. Verify that they are always reading/writing + // consistent data. + const int kNumThreads = 5; + HANDLE threads[kNumThreads]; + + // Spawn the threads. + for (int16 index = 0; index < kNumThreads; index++) { + void *argument = reinterpret_cast<void*>(index); + unsigned thread_id; + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, MultipleThreadMain, argument, 0, &thread_id)); + EXPECT_NE(threads[index], static_cast<HANDLE>(NULL)); + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], 60*1000); + EXPECT_EQ(rv, WAIT_OBJECT_0); // verify all threads finished + CloseHandle(threads[index]); + } +} + + +TEST(SharedMemoryTest, Lock) { + // 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. + const int kNumThreads = 5; + HANDLE threads[kNumThreads]; + + // Spawn the threads. + for (int index = 0; index < kNumThreads; ++index) { + void *argument = reinterpret_cast<void*>(static_cast<INT_PTR>(index)); + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, &MultipleLockThread, argument, 0, NULL)); + EXPECT_NE(threads[index], static_cast<HANDLE>(NULL)); + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; ++index) { + DWORD rv = WaitForSingleObject(threads[index], 60*1000); + EXPECT_EQ(rv, WAIT_OBJECT_0); // verify all threads finished + CloseHandle(threads[index]); + } +} diff --git a/base/singleton.h b/base/singleton.h new file mode 100644 index 0000000..47c0c1d --- /dev/null +++ b/base/singleton.h @@ -0,0 +1,293 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SINGLETON_H__ +#define BASE_SINGLETON_H__ + +#include <stdlib.h> + +#include <utility> + +#include "base/lock.h" +#include "base/singleton_internal.h" + +#ifdef WIN32 +#include "base/fix_wp64.h" +#else // WIN32 +#include <pthread.h> +#endif // WIN32 + +// Default traits for Singleton<Type>. Calls operator new and operator delete on +// the object. Registers automatic deletion at library unload or process exit. +// Overload if you need arguments or another memory allocation function. +template<typename Type> +struct DefaultSingletonTraits { + // Allocates the object. + static Type* New() { + // The parenthesis is very important here; it forces POD type + // initialization. + return new Type(); + } + + // Destroys the object. + static void Delete(Type* x) { + delete x; + } + + // Set to true to automatically register deletion of the object on library + // unload or process exit. + static const bool kRegisterAtExit = true; + + // Note: Only apply on Windows. Has *no effect* on other platform. + // When set to true, it signals that Trait::New() *must* not be called + // multiple times at construction. Anything that must be done to not enter + // this situation should be done at all cost. This simply involves creating a + // temporary lock. + static const bool kMustCallNewExactlyOnce = false; +}; + + +// The Singleton<Type, Traits, DifferentiatingType> class manages a single +// instance of Type which will be created on first use and will be destroyed at +// library unload (or on 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 usages: (none are preferred, they all result in the same code) +// 1. FooClass* ptr = Singleton<FooClass>::get(); +// ptr->Bar(); +// 2. Singleton<FooClass>()->Bar(); +// 3. Singleton<FooClass>::get()->Bar(); +// +// Singleton<> has no non-static members and doesn't need to actually be +// instantiated. It does no harm to instantiate it and use it as a class member +// or at global level since it is acting as a POD type. +// +// 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: +// MCNEO = kMustCallNewExactlyOnce +// RAE = kRegisterAtExit +// +// On every platform, if Traits::RAE is true, the singleton will be destroyed at +// library unload or process exit. if Traits::RAE is false, the singleton will +// not be freed at library unload or 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. +// +// On Windows, now the fun begins. Traits::New() may be called more than once +// concurrently, but no user will gain access to the object until the winning +// Traits::New() call is completed. +// +// On Windows, if Traits::MCNEO and Traits::RAE are both false, +// Traits::Delete() can still be called. The reason is that a race condition can +// occur during the object creation which will cause Traits::Delete() to be +// called even if Traits::RAE is false, so Traits::Delete() should still be +// implemented or objects may be leaked when there is a race condition in +// creating the singleton. Even though this case is very rare, it may happen in +// practice. To work around this situation, before creating a multithreaded +// environment, be sure to call Singleton<>::get() to force the creation of the +// instance. +// +// On Windows, If Traits::MCNEO is true, a temporary lock per singleton will be +// created to ensure that Trait::New() is only called once. +// +// If you want to ensure that your class can only exist as a singleton, make +// its constructors private, and make DefaultSingletonTraits<> a friend: +// +// #include "base/singleton.h" +// class FooClass { +// public: +// void Bar() { ... } +// private: +// FooClass() { ... } +// friend DefaultSingletonTraits<FooClass>; +// +// DISALLOW_EVIL_CONSTRUCTORS(FooClass); +// }; +// +// 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. +// +// (c) On Windows at least, if Traits::kMustCallNewExactlyOnce is false, +// Traits::New() may be called two times in two different threads at the +// same time so it must not have side effects. Set +// Traits::kMustCallNewExactlyOnce to true to alleviate this issue, at +// the cost of a slight increase of memory use and creation time. +// +template <typename Type, + typename Traits = DefaultSingletonTraits<Type>, + typename DifferentiatingType = Type> +class Singleton + : public SingletonStorage< + Type, + std::pair<Traits, DifferentiatingType>, + UseVolatileSingleton<Traits::kMustCallNewExactlyOnce>::value> { + public: + // 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() { + Type* value = instance_; + // Acute readers may think: why not just discard "value" and use + // "instance_" directly? Astute readers will remark that instance_ can be a + // volatile pointer on Windows and hence the compiler would be forced to + // generate two memory reads instead of just one. Since this is the hotspot, + // this is inefficient. + if (value) + return value; + +#ifdef WIN32 + // Statically determine which function to call. + LockedConstruct<Traits::kMustCallNewExactlyOnce>(); +#else // WIN32 + // Posix platforms already have the functionality embedded. + pthread_once(&control_, SafeConstruct); +#endif // WIN32 + return instance_; + } + + // Shortcuts. + Type& operator*() { + return *get(); + } + + Type* operator->() { + return get(); + } + + private: +#ifdef WIN32 + // Use bool template differentiation to make sure to not build the other part + // of the code. We don't want to instantiate Singleton<Lock, ...> uselessly. + template<bool kUseLock> + static void LockedConstruct() { + // Define a differentiating type for the Lock. + typedef std::pair<Type, std::pair<Traits, DifferentiatingType> > + LockDifferentiatingType; + + // Object-type lock. Note that the lock singleton is different per singleton + // type. + AutoLock lock(*Singleton<Lock, + DefaultSingletonTraits<Lock>, + LockDifferentiatingType>()); + // Now that we have the lock, look if the instance is created, if not yet, + // create it. + if (!instance_) + SafeConstruct(); + } + + template<> + static void LockedConstruct<false>() { + // Implemented using atomic compare-and-swap. The new object is + // constructed and used as the new value in the operation; if the + // compare fails, the new object will be deleted. Future implementations + // for Windows might use InitOnceExecuteOnce (Vista-only), similar in + // spirit to pthread_once. + + // On Windows, multiple concurrent Traits::New() calls are tolerated. + Type* value = Traits::New(); + if (InterlockedCompareExchangePointer( + reinterpret_cast<void* volatile*>(&instance_), value, NULL)) { + // Race condition, discard the temporary value. + Traits::Delete(value); + } else { + // Got it, register destruction at unload. atexit() is called on library + // unload. It is assumed that atexit() is itself thread safe. It is also + // assumed that registered functions by atexit are called in a thread + // safe manner. At least on Windows, they are called with the loader + // lock held. On Windows, the CRT use a structure similar to + // std::map<dll_handle,std::vector<registered_functions>> so the right + // functions are called on library unload, independent of having a DLL + // CRT or a static CRT or even both. + if (Traits::kRegisterAtExit) + atexit(&OnExit); + } + } +#endif // WIN32 + + // SafeConstruct is guaranteed to be executed only once. + static void SafeConstruct() { + instance_ = Traits::New(); + + // Porting note: this code depends on some properties of atexit which are + // not guaranteed by the standard: + // - atexit must be thread-safe: its internal manipulation of the list of + // registered functions must be tolerant of multiple threads attempting + // to register exit routines simultaneously. + // - exit routines must run when the executable module that contains them + // is unloaded. For routines in by dynamically-loaded modules, this + // may be sooner than process termination. + // - atexit should support an arbitrary number of registered exit + // routines, or at least should support more routines than will + // actually be registered (the standard only requires 32). + // The atexit implementations in contemporary versions of Mac OS X, glibc, + // and the Windows C runtime provide these capabilities. To port to other + // systems with less-advanced (even though still standard-conforming) + // atexit implmentations, consider alternatives such as __cxa_atexit or + // custom termination sections. + if (Traits::kRegisterAtExit) + atexit(OnExit); + } + + // Adapter function for use with atexit(). + static void OnExit() { + if (!instance_) + return; + Traits::Delete(instance_); + instance_ = NULL; + } + +#ifndef WIN32 + static pthread_once_t control_; +#endif // !WIN32 +}; + +#ifndef WIN32 + +template <typename Type, typename Traits, typename DifferentiatingType> +pthread_once_t Singleton<Type, Traits, DifferentiatingType>::control_ = + PTHREAD_ONCE_INIT; + +#endif // !WIN32 + +#endif // BASE_SINGLETON_H__ diff --git a/base/singleton_dll_unittest.cc b/base/singleton_dll_unittest.cc new file mode 100644 index 0000000..c663db3 --- /dev/null +++ b/base/singleton_dll_unittest.cc @@ -0,0 +1,114 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/singleton_dll_unittest.h" +#include "base/logging.h" + +BOOL APIENTRY DllMain(HMODULE module, DWORD reason_for_call, LPVOID reserved) { + switch (reason_for_call) { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(module); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +COMPILE_ASSERT(DefaultSingletonTraits<int>::kRegisterAtExit == true, a); +COMPILE_ASSERT( + DefaultSingletonTraits<int>::kMustCallNewExactlyOnce == false, + b); + +template<typename Type> +struct LockTrait : public DefaultSingletonTraits<Type> { + static const bool kMustCallNewExactlyOnce = true; +}; + +struct Init5Trait : public DefaultSingletonTraits<int> { + static int* New() { + return new int(5); + } +}; + +struct CallbackTrait : public CustomAllocTrait<CallBackFunc> { + static void Delete(CallBackFunc* p) { + if (*p) + (*p)(); + CHECK(CustomAllocTrait<CallBackFunc>::Delete(p)); + } +}; + +struct NoLeakTrait : public CallbackTrait { +}; + +struct LeakTrait : public CallbackTrait { + static const bool kRegisterAtExit = false; +}; + +SINGLETON_UNITTEST_API int* WINAPI SingletonInt1() { + return Singleton<int>::get(); +} + +SINGLETON_UNITTEST_API int* WINAPI SingletonInt2() { + // Force to use a different singleton than SingletonInt1. + return Singleton<int, DefaultSingletonTraits<int> >::get(); +} + +class DummyDifferentiatingClass { +}; + +SINGLETON_UNITTEST_API int* WINAPI SingletonInt3() { + // Force to use a different singleton than SingletonInt1 and SingletonInt2. + // Note that any type can be used; int, float, std::wstring... + return Singleton<int, DefaultSingletonTraits<int>, + DummyDifferentiatingClass>::get(); +} + +SINGLETON_UNITTEST_API int* WINAPI SingletonInt4() { + return Singleton<int, LockTrait<int> >::get(); +} + +SINGLETON_UNITTEST_API int* WINAPI SingletonInt5() { + return Singleton<int, Init5Trait>::get(); +} + +SINGLETON_UNITTEST_API void WINAPI SingletonNoLeak(CallBackFunc CallOnQuit) { + *Singleton<CallBackFunc, NoLeakTrait>::get() = CallOnQuit; +} + +SINGLETON_UNITTEST_API void WINAPI SingletonLeak(CallBackFunc CallOnQuit) { + *Singleton<CallBackFunc, LeakTrait>::get() = CallOnQuit; +} + +SINGLETON_UNITTEST_API CallBackFunc* WINAPI GetLeakySingleton() { + return Singleton<CallBackFunc, LeakTrait>::get(); +} diff --git a/base/singleton_dll_unittest.h b/base/singleton_dll_unittest.h new file mode 100644 index 0000000..d161722 --- /dev/null +++ b/base/singleton_dll_unittest.h @@ -0,0 +1,83 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SINGLETON_DLL_UNITTEST_H__ +#define BASE_SINGLETON_DLL_UNITTEST_H__ + +#include "base/singleton.h" + +#ifdef SINGLETON_UNITTEST_EXPORTS +#define SINGLETON_UNITTEST_API __declspec(dllexport) +#else +#define SINGLETON_UNITTEST_API __declspec(dllimport) +#endif + +// Function pointer for singleton getters. +typedef int* (WINAPI* SingletonIntFunc)(); + +// Callback function to be called on library unload. +typedef void (WINAPI* CallBackFunc)(); + +// Leaky/nonleak singleton initialization. +typedef void (WINAPI* LeakySingletonFunc)(CallBackFunc); + +// Retrieve the leaky singleton for later disposal. +typedef CallBackFunc* (WINAPI* GetLeakySingletonFunc)(); + +// When using new/delete, the heap is destroyed on library unload. So use +// VirtualAlloc/VirtualFree to bypass this behavior. +template<typename Type> +struct CustomAllocTrait : public DefaultSingletonTraits<Type> { + static Type* New() { + return static_cast<Type*>(VirtualAlloc(NULL, sizeof(Type), MEM_COMMIT, + PAGE_READWRITE)); + } + + static bool Delete(Type* p) { + return 0!=VirtualFree(p, 0, MEM_RELEASE); + } +}; + +// 1 and 2 share the same instance. +// 3 simply use a different key. +// 4 sets kMustCallNewExactlyOnce to true. +// 5 default initialize to 5. +extern "C" SINGLETON_UNITTEST_API int* WINAPI SingletonInt1(); +extern "C" SINGLETON_UNITTEST_API int* WINAPI SingletonInt2(); +extern "C" SINGLETON_UNITTEST_API int* WINAPI SingletonInt3(); +extern "C" SINGLETON_UNITTEST_API int* WINAPI SingletonInt4(); +extern "C" SINGLETON_UNITTEST_API int* WINAPI SingletonInt5(); + +extern "C" SINGLETON_UNITTEST_API void WINAPI SingletonNoLeak( + CallBackFunc CallOnQuit); +extern "C" SINGLETON_UNITTEST_API void WINAPI SingletonLeak( + CallBackFunc CallOnQuit); +extern "C" SINGLETON_UNITTEST_API CallBackFunc* WINAPI GetLeakySingleton(); + +#endif // BASE_SINGLETON_DLL_UNITTEST_H__ diff --git a/base/singleton_internal.h b/base/singleton_internal.h new file mode 100644 index 0000000..82fce94 --- /dev/null +++ b/base/singleton_internal.h @@ -0,0 +1,66 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_SINGLETON_INTERNAL_H__ +#define BASE_SINGLETON_INTERNAL_H__ + +// Define the storage of the singleton pointer. +template <typename Type, typename DifferentiatingType, bool kVolatile> +class SingletonStorage { + protected: + // The actual pointer. Note that it is a volatile pointer. + static Type* volatile instance_; +}; + +// Partial specialization. +template <typename Type, typename DifferentiatingType> +class SingletonStorage<Type, DifferentiatingType, false> { + protected: + // The pointer does not need to be volatile for use with pthread_once or + // locked initialization. + static Type* instance_; +}; + +template <typename Type, typename DifferentiatingType, bool kVolatile> +Type* volatile SingletonStorage<Type, DifferentiatingType, + kVolatile>::instance_ = NULL; + +template <typename Type, typename DifferentiatingType> +Type* SingletonStorage<Type, DifferentiatingType, false>::instance_ = NULL; + +template<bool kUseVolatile> +struct UseVolatileSingleton { +#ifdef WIN32 + static const bool value = kUseVolatile; +#else + static const bool value = false; +#endif // WIN32 +}; + +#endif // BASE_SINGLETON_INTERNAL_H__ diff --git a/base/singleton_unittest.cc b/base/singleton_unittest.cc new file mode 100644 index 0000000..5d21593 --- /dev/null +++ b/base/singleton_unittest.cc @@ -0,0 +1,227 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/singleton_dll_unittest.h" +#include "base/file_util.h" +#include "base/path_service.h" + +class SingletonTest : public testing::Test { + public: + SingletonTest() { + } + + virtual void SetUp() { + module_ = NULL; + non_leak_called_ = false; + leaky_called_ = false; + } + + virtual void TearDown() { + ASSERT_FALSE(module_); + } + + static bool IsTestCaseDisabled() { + // Check if the dll exists beside the executable. + std::wstring path; + PathService::Get(base::DIR_EXE, &path); + file_util::AppendToPath(&path, kLibrary); + return !file_util::PathExists(path); + } + + protected: + void LoadLibrary() { + ASSERT_FALSE(module_); + module_ = ::LoadLibrary(kLibrary); + ASSERT_TRUE(module_ != NULL); + } + + void FreeLibrary() { + ASSERT_TRUE(module_ != NULL); + ASSERT_TRUE(::FreeLibrary(module_)); + module_ = NULL; + } + + template<typename T> + void GetProc(const char* function_name, T* function) { + ASSERT_TRUE(module_ != NULL); + *function = reinterpret_cast<T>(GetProcAddress(module_, function_name)); + ASSERT_TRUE(*function); + } + + void VerifiesCallbacks() { + EXPECT_TRUE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + non_leak_called_ = false; + leaky_called_ = false; + } + + void VerifiesCallbacksNotCalled() { + EXPECT_FALSE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + non_leak_called_ = false; + leaky_called_ = false; + } + + static void WINAPI CallbackNoLeak() { + non_leak_called_ = true; + } + + static void WINAPI CallbackLeak() { + leaky_called_ = true; + } + + private: + static const wchar_t* const kLibrary; + HMODULE module_; + static bool non_leak_called_; + static bool leaky_called_; +}; + +bool SingletonTest::non_leak_called_ = false; +bool SingletonTest::leaky_called_ = false; + +const wchar_t* const SingletonTest::kLibrary = L"singleton_dll_unittest.dll"; + +TEST_F(SingletonTest, Basic) { + if (IsTestCaseDisabled()) + return; + + int* singleton_int_1; + int* singleton_int_2; + int* singleton_int_3; + int* singleton_int_4; + int* singleton_int_5; + CallBackFunc* leaky_singleton; + + LoadLibrary(); + { + SingletonIntFunc sut1; + SingletonIntFunc sut2; + SingletonIntFunc sut3; + SingletonIntFunc sut4; + SingletonIntFunc sut5; + { + GetProc("SingletonInt1", &sut1); + singleton_int_1 = sut1(); + } + // Ensure POD type initialization. + EXPECT_EQ(*singleton_int_1, 0); + *singleton_int_1 = 1; + + EXPECT_EQ(singleton_int_1, sut1()); + EXPECT_EQ(*singleton_int_1, 1); + + { + GetProc("SingletonInt2", &sut2); + singleton_int_2 = sut2(); + } + // Same instance that 1. + EXPECT_EQ(*singleton_int_2, 1); + EXPECT_EQ(singleton_int_1, singleton_int_2); + + { + GetProc("SingletonInt3", &sut3); + singleton_int_3 = sut3(); + } + // Different instance than 1 and 2. + EXPECT_EQ(*singleton_int_3, 0); + EXPECT_NE(singleton_int_1, singleton_int_3); + *singleton_int_3 = 3; + EXPECT_EQ(*singleton_int_1, 1); + EXPECT_EQ(*singleton_int_2, 1); + + { + GetProc("SingletonInt4", &sut4); + singleton_int_4 = sut4(); + } + // Use a lock for creation. Not really tested at length. + EXPECT_EQ(*singleton_int_4, 0); + *singleton_int_4 = 4; + EXPECT_NE(singleton_int_1, singleton_int_4); + EXPECT_NE(singleton_int_3, singleton_int_4); + + { + GetProc("SingletonInt5", &sut5); + singleton_int_5 = sut5(); + } + // Is default initialized to 5. + EXPECT_EQ(*singleton_int_5, 5); + EXPECT_NE(singleton_int_1, singleton_int_5); + EXPECT_NE(singleton_int_3, singleton_int_5); + EXPECT_NE(singleton_int_4, singleton_int_5); +#ifdef _DEBUG + // In release, the optimizer may make both exports use exactly the same + // code. + EXPECT_NE(sut1, sut2); +#endif + EXPECT_NE(sut2, sut3); + EXPECT_NE(sut3, sut4); + EXPECT_NE(sut4, sut5); + + LeakySingletonFunc noleak; + GetProc("SingletonNoLeak", &noleak); + noleak(&CallbackNoLeak); + LeakySingletonFunc leak; + GetProc("SingletonLeak", &leak); + leak(&CallbackLeak); + GetLeakySingletonFunc get_leaky; + GetProc("GetLeakySingleton", &get_leaky); + leaky_singleton = get_leaky(); + EXPECT_TRUE(leaky_singleton); + } + FreeLibrary(); + + // Verify that only the expected callback has been called. + VerifiesCallbacks(); + // Delete the leaky singleton. It is interesting to note that Purify does + // *not* detect the leak when this call is commented out. :( + EXPECT_TRUE(CustomAllocTrait<CallBackFunc>::Delete(leaky_singleton)); + + LoadLibrary(); + { + // Verifiy that the variables were reset. + { + SingletonIntFunc sut1; + GetProc("SingletonInt1", &sut1); + singleton_int_1 = sut1(); + EXPECT_EQ(*singleton_int_1, 0); + } + { + SingletonIntFunc sut5; + GetProc("SingletonInt5", &sut5); + singleton_int_5 = sut5(); + EXPECT_EQ(*singleton_int_5, 5); + } + } + // The leaky singleton shouldn't leak since SingletonLeak has not been called. + FreeLibrary(); + + VerifiesCallbacksNotCalled(); +} diff --git a/base/spin_wait.h b/base/spin_wait.h new file mode 100644 index 0000000..7226387 --- /dev/null +++ b/base/spin_wait.h @@ -0,0 +1,74 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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_SPIN_WAIT_H__ +#define BASE_SPIN_WAIT_H__ + +#include "base/time.h" + +// Provide a macro that will wait no longer than 1 second for an asynchronous +// change is the value of an expression. +// A typical use would be: +// +// SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(0 == f(x)); +// +// The expression will be evaluated repeatedly until it is true, or until +// the time (1 second) expires. +// Since tests generally have a 5 second watch dog timer, this spin loop is +// typically used to get the padding needed on a given test platform to assure +// that the test passes, even if load varies, and external events vary. + +#define SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(expression) \ + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromSeconds(1), (expression)) + +#define SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(delta, expression) do { \ + Time start = Time::Now(); \ + const TimeDelta kTimeout = delta; \ + while(!(expression)) { \ + if (kTimeout < Time::Now() - start) { \ + EXPECT_LE((Time::Now() - start).InMilliseconds(), \ + kTimeout.InMilliseconds()) << "Timed out"; \ + break; \ + } \ + Sleep(50); \ + } \ + } \ + while(0) + +#endif // BASE_SPIN_WAIT_H__ diff --git a/base/stack_container.h b/base/stack_container.h new file mode 100644 index 0000000..16c6ab7 --- /dev/null +++ b/base/stack_container.h @@ -0,0 +1,255 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_STACK_CONTAINER_H__ +#define BASE_STACK_CONTAINER_H__ + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template<typename T, size_t stack_capacity> +class StackAllocator : public std::allocator<T> { + public: + // 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 reinterpret_cast<T*>(stack_buffer_); } + const T* stack_buffer() const { + return reinterpret_cast<const T*>(stack_buffer_); + } + + // + // IMPORTANT: Take care to ensure that stack_buffer_ is aligned + // since it is used to mimic an array of T. + // Be careful while declaring any unaligned types (like bool) + // before stack_buffer_. + // + + // 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. + char stack_buffer_[sizeof(T[stack_capacity])]; + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template<typename U> + struct rebind { + typedef StackAllocator<U, stack_capacity> other; + }; + + StackAllocator(Source* source) : source_(source) { + } + StackAllocator(const StackAllocator& other) : source_(other.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_->used_stack_buffer_ && n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator<T>::allocate(n, hint); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator<T>::deallocate(p, n); + } + + private: + Source* source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template<typename ContainerType, int stack_capacity> +class StackContainer { + public: + typedef typename ContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator<ContainedType, stack_capacity> Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType& container() { return container_; } + const ContainerType& container() const { return container_; } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType* operator->() { return &container_; } + const ContainerType* operator->() const { return &container_; } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + typename const Allocator::Source& stack_data() const { + return stack_data_; + } +#endif + + protected: + typename Allocator::Source stack_data_; + Allocator allocator_; + ContainerType container_; + + DISALLOW_EVIL_CONSTRUCTORS(StackContainer); +}; + +// StackString +template<size_t stack_capacity> +class StackString : public StackContainer< + std::basic_string<char, + std::char_traits<char>, + StackAllocator<char, stack_capacity> >, + stack_capacity> { + public: + StackString() : StackContainer< + std::basic_string<char, + std::char_traits<char>, + StackAllocator<char, stack_capacity> >, + stack_capacity>() { + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(StackString); +}; + +// StackWString +template<size_t stack_capacity> +class StackWString : public StackContainer< + std::basic_string<wchar_t, + std::char_traits<wchar_t>, + StackAllocator<wchar_t, stack_capacity> >, + stack_capacity> { + public: + StackWString() : StackContainer< + std::basic_string<wchar_t, + std::char_traits<wchar_t>, + StackAllocator<wchar_t, stack_capacity> >, + stack_capacity>() { + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(StackWString); +}; + +// StackVector +// +// Example: +// StackVector<int, 16> foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template<typename T, size_t stack_capacity> +class StackVector : public StackContainer< + std::vector<T, StackAllocator<T, stack_capacity> >, + stack_capacity> { + public: + StackVector() : StackContainer< + std::vector<T, StackAllocator<T, stack_capacity> >, + stack_capacity>() { + } + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector<T, stack_capacity>& other) + : StackContainer< + std::vector<T, StackAllocator<T, stack_capacity> >, + stack_capacity>() { + container().assign(other->begin(), other->end()); + } + + StackVector<T, stack_capacity>& operator=( + const StackVector<T, stack_capacity>& other) { + 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 container().operator[](i); } + const T& operator[](size_t i) const { return container().operator[](i); } +}; + +#endif // BASE_STACK_CONTAINER_H__ diff --git a/base/stack_container_unittest.cc b/base/stack_container_unittest.cc new file mode 100644 index 0000000..aa8f34a --- /dev/null +++ b/base/stack_container_unittest.cc @@ -0,0 +1,137 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/stack_container.h" + +#include <algorithm> + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/ref_counted.h" + +namespace { + +class Dummy : public base::RefCounted<Dummy> { + public: + Dummy(int* alive) : alive_(alive) { + ++*alive_; + } + ~Dummy() { + --*alive_; + } + private: + int* const alive_; +}; + +} // namespace + +TEST(StackContainer, Vector) { + const int stack_size = 3; + StackVector<int, stack_size> vect; + const int* stack_buffer = &vect.stack_data().stack_buffer()[0]; + + // The initial |stack_size| elements should appear in the stack buffer. + EXPECT_EQ(stack_size, vect.container().capacity()); + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i); + EXPECT_EQ(stack_buffer, &vect.container()[0]); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + } + + // Adding more elements should push the array onto the heap. + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i + stack_size); + EXPECT_NE(stack_buffer, &vect.container()[0]); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + } + + // The array should still be in order. + for (int i = 0; i < stack_size * 2; i++) + EXPECT_EQ(i, vect.container()[i]); + + // Resize to smaller. Our STL implementation won't reallocate in this case, + // otherwise it might use our stack buffer. We reserve right after the resize + // to guarantee it isn't using the stack buffer, even though it doesn't have + // much data. + vect.container().resize(stack_size); + vect.container().reserve(stack_size * 2); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + + // Copying the small vector to another should use the same allocator and use + // the now-unused stack buffer. GENERALLY CALLERS SHOULD NOT DO THIS since + // they have to get the template types just right and it can cause errors. + std::vector<int, StackAllocator<int, stack_size> > other(vect.container()); + EXPECT_EQ(stack_buffer, &other.front()); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + for (int i = 0; i < stack_size; i++) + EXPECT_EQ(i, other[i]); +} + +TEST(StackContainer, VectorDoubleDelete) { + // Regression testing for double-delete. + typedef StackVector<scoped_refptr<Dummy>, 2> Vector; + typedef Vector::ContainerType Container; + Vector vect; + + int alive = 0; + scoped_refptr<Dummy> dummy(new Dummy(&alive)); + EXPECT_EQ(alive, 1); + + vect->push_back(dummy); + EXPECT_EQ(alive, 1); + + Dummy* dummy_unref = dummy.get(); + dummy = NULL; + EXPECT_EQ(alive, 1); + + Container::iterator itr = std::find(vect->begin(), vect->end(), dummy_unref); + EXPECT_EQ(itr->get(), dummy_unref); + vect->erase(itr); + EXPECT_EQ(alive, 0); + + // Shouldn't crash at exit. +} + +TEST(StackContainer, BufferAlignment) { + StackVector<wchar_t, 16> text; + text->push_back(L'A'); + text->push_back(L'B'); + text->push_back(L'C'); + text->push_back(L'D'); + text->push_back(L'E'); + text->push_back(L'F'); + text->push_back(0); + + const wchar_t* buffer = &text[1]; + bool even_aligned = (0 == (((size_t)buffer) & 0x1)); + EXPECT_EQ(even_aligned, true); +} + +// Make sure all the class compiles correctly. +template StackVector<int, 2>; +template StackVector<scoped_refptr<Dummy>, 2>; diff --git a/base/stats_counters.h b/base/stats_counters.h new file mode 100644 index 0000000..778b824 --- /dev/null +++ b/base/stats_counters.h @@ -0,0 +1,297 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#ifndef BASE_STATS_COUNTERS_H__ +#define BASE_STATS_COUNTERS_H__ + +#include <string> +#include "base/stats_table.h" +#include "base/time.h" + +// 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 { \ + static StatsCounter counter(name); \ + counter.Add(delta); \ +} while (0) + +#define SIMPLE_STATS_COUNTER(name) STATS_COUNTER(name, 1) + +#define RATE_COUNTER(name, duration) do { \ + static 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 StatsCounter { + public: + // Create a StatsCounter object. + explicit StatsCounter(const std::wstring& name) + : counter_id_(-1) { + // We prepend the name with 'c:' to indicate that it is a counter. + name_ = L"c:"; + name_.append(name); + }; + + virtual ~StatsCounter() {} + + // Sets the counter to a specific value. + void Set(int value) { + int* loc = GetPtr(); + if (loc) *loc = value; + } + + // Increments the counter. + void Increment() { + Add(1); + } + + // TODO(jar) temporary hack include method till base and chrome use new name. + void Increment(int value) { + Add(value); + } + + virtual void Add(int value) { + int* loc = GetPtr(); + if (loc) + (*loc) += value; + } + + // Decrements the counter. + void Decrement() { + Add(-1); + } + + void Subtract(int value) { + Add(-value); + } + + // TODO(jar) temporary hack includes method till base and chrome use new name. + void Decrement(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() + : counter_id_(-1) { + } + + // Returns the cached address of this counter location. + int* 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(L"")) { + // 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; + } + + std::wstring 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 StatsCounterTimer : protected StatsCounter { + public: + // Constructs and starts the timer. + explicit StatsCounterTimer(const std::wstring& name) { + // we prepend the name with 't:' to indicate that it is a timer. + name_ = L"t:"; + name_.append(name); + } + + // Start the timer. + void Start() { + if (!Enabled()) + return; + start_time_ = TimeTicks::Now(); + stop_time_ = TimeTicks(); + } + + // Stop the timer and record the results. + void Stop() { + if (!Enabled() || !Running()) + return; + stop_time_ = TimeTicks::Now(); + Record(); + } + + // Returns true if the timer is running. + bool Running() { + return Enabled() && !start_time_.is_null() && stop_time_.is_null(); + } + + // Accept a TimeDelta to increment. + virtual void AddTime(TimeDelta time) { + Add(static_cast<int>(time.InMilliseconds())); + } + + // TODO(jar) temporary hack include method till base and chrome use new name. + void IncrementTimer(TimeDelta time) { + AddTime(time); + } + + protected: + // Compute the delta between start and stop, in milliseconds. + void Record() { + AddTime(stop_time_ - start_time_); + } + + 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 StatsRate : public StatsCounterTimer { + public: + // Constructs and starts the timer. + explicit StatsRate(const wchar_t* name) + : StatsCounterTimer(name), + counter_(name), + largest_add_(std::wstring(L" ").append(name).append(L"MAX").c_str()) { + } + + virtual void Add(int value) { + counter_.Increment(); + StatsCounterTimer::Add(value); + if (value > largest_add_.value()) + largest_add_.Set(value); + } + + private: + StatsCounter counter_; + StatsCounter largest_add_; +}; + + +// Helper class for scoping a timer or rate. +template<class T> class StatsScope { + public: + explicit StatsScope<T>(T& timer) + : timer_(timer) { + timer_.Start(); + } + + ~StatsScope() { + timer_.Stop(); + } + + void Stop() { + timer_.Stop(); + } + + private: + T& timer_; +}; + +#endif // BASE_STATS_COUNTERS_H__ diff --git a/base/stats_table.cc b/base/stats_table.cc new file mode 100644 index 0000000..8213c34 --- /dev/null +++ b/base/stats_table.cc @@ -0,0 +1,545 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/stats_table.h" + +#include "base/logging.h" +#include "base/thread_local_storage.h" + +// 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 wchar_t kUnknownName[] = L"<unknown>"; + +// Various header information contained in the memory mapped segment. +struct TableHeader { + int version; + int size; + int max_counters; + int max_threads; +}; + +// 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); +} + +// StatsTableTLSData 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 StatsTableTLSData stored in its TLS. +struct StatsTableTLSData { + StatsTable* table; + int slot; +}; + +// The SlotReturnFunction is called at thread exit for each thread +// which used the StatsTable. +static void SlotReturnFunction(void* data) { + StatsTableTLSData* tls_data = static_cast<StatsTableTLSData*>(data); + if (tls_data) { + DCHECK(tls_data->table); + tls_data->table->UnregisterThread(); + } +} + +} // namespace + +// The StatsTablePrivate maintains convenience pointers into the +// shared memory segment. Use this class to keep the data structure +// clean and accessible. +class StatsTablePrivate { + public: + // Create the StatsTablePrivate based on expected size parameters. + StatsTablePrivate(void* memory, int size, int max_threads, int max_counters); + + // 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 + wchar_t* thread_name(int slot_id) const { + return &thread_names_table_[ + (slot_id-1) * (StatsTable::kMaxThreadNameLength)]; + } + int* 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]); + } + wchar_t* 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: + // 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); + + TableHeader* table_header_; + wchar_t* thread_names_table_; + int* thread_tid_table_; + int* thread_pid_table_; + wchar_t* counter_names_table_; + int* data_table_; +}; + +StatsTablePrivate::StatsTablePrivate(void* memory, int size, int max_threads, + int max_counters) { + TableHeader* header = static_cast<TableHeader*>(memory); + // If the version does not match, then assume the table needs + // to be initialized. + if (header->version != kTableVersion) + InitializeTable(memory, size, max_counters, max_threads); + + // We have a valid table, so compute our pointers. + ComputeMappedPointers(memory); +} + +void StatsTablePrivate::InitializeTable(void* memory, int size, + int max_counters, + int max_threads) { + // Zero everything. + memset(memory, 0, size); + + // Initialize the header. + TableHeader* header = static_cast<TableHeader*>(memory); + header->version = kTableVersion; + header->size = size; + header->max_counters = max_counters; + header->max_threads = max_threads; +} + +void StatsTablePrivate::ComputeMappedPointers(void* memory) { + char* data = static_cast<char*>(memory); + int offset = 0; + + table_header_ = reinterpret_cast<TableHeader*>(data); + offset += sizeof(*table_header_); + offset += AlignOffset(offset); + + // Verify we're looking at a valid StatsTable. + DCHECK_EQ(table_header_->version, kTableVersion); + + thread_names_table_ = reinterpret_cast<wchar_t*>(data + offset); + offset += sizeof(wchar_t) * + max_threads() * StatsTable::kMaxThreadNameLength; + offset += AlignOffset(offset); + + thread_tid_table_ = reinterpret_cast<int*>(data + offset); + offset += sizeof(int) * max_threads(); + offset += AlignOffset(offset); + + thread_pid_table_ = reinterpret_cast<int*>(data + offset); + offset += sizeof(int) * max_threads(); + offset += AlignOffset(offset); + + counter_names_table_ = reinterpret_cast<wchar_t*>(data + offset); + offset += sizeof(wchar_t) * + max_counters() * StatsTable::kMaxCounterNameLength; + offset += AlignOffset(offset); + + data_table_ = reinterpret_cast<int*>(data + offset); + offset += sizeof(int) * max_threads() * max_counters(); + + DCHECK_EQ(offset, size()); +} + + + +// We keep a singleton table which can be easily accessed. +StatsTable* StatsTable::global_table_ = NULL; + +StatsTable::StatsTable(const std::wstring& name, int max_threads, + int max_counters) + : tls_index_(ThreadLocalStorage::Alloc(SlotReturnFunction)) { + int table_size = + AlignedSize(sizeof(TableHeader)) + + AlignedSize((max_counters * sizeof(wchar_t) * kMaxCounterNameLength)) + + AlignedSize((max_threads * sizeof(wchar_t) * kMaxThreadNameLength)) + + AlignedSize(max_threads * sizeof(int)) + + AlignedSize(max_threads * sizeof(int)) + + AlignedSize((sizeof(int) * (max_counters * max_threads))); + + impl_ = NULL; + // TODO(mbelshe): Move this out of the constructor + if (shared_memory_.Create(name, false, true, table_size)) + if (shared_memory_.Map(table_size)) + impl_ = new StatsTablePrivate(shared_memory_.memory(), table_size, + max_threads, max_counters); + if (!impl_) + LOG(ERROR) << "StatsTable did not initialize:" << GetLastError(); +} + +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. + ThreadLocalStorage::Free(tls_index_); + + // Cleanup our shared memory. + delete impl_; + + // If we are the global table, unregister ourselves. + if (global_table_ == this) + global_table_ = NULL; +} + +int StatsTable::RegisterThread(const std::wstring& name) { + int slot = 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(&shared_memory_); + slot = FindEmptyThread(); + if (!slot) { + return 0; + } + + DCHECK(impl_); + + // We have space, so consume a column in the table. + std::wstring thread_name = name; + if (name.empty()) + thread_name = kUnknownName; + wcsncpy_s(impl_->thread_name(slot), kMaxThreadNameLength, + thread_name.c_str(), _TRUNCATE); + *(impl_->thread_tid(slot)) = GetCurrentThreadId(); + *(impl_->thread_pid(slot)) = GetCurrentProcessId(); + } + + // Set our thread local storage. + StatsTableTLSData* data = new StatsTableTLSData; + data->table = this; + data->slot = slot; + ThreadLocalStorage::Set(tls_index_, data); + return slot; +} + +StatsTableTLSData* StatsTable::GetTLSData() const { + StatsTableTLSData* data = + static_cast<StatsTableTLSData*>(ThreadLocalStorage::Get(tls_index_)); + if (!data) + return NULL; + + DCHECK(data->slot); + DCHECK_EQ(data->table, this); + return data; +} + +void StatsTable::UnregisterThread() { + StatsTableTLSData* data = GetTLSData(); + if (!data) + return; + DCHECK(impl_); + + // Mark the slot free by zeroing out the thread name. + wchar_t* name = impl_->thread_name(data->slot); + *name = L'\0'; + + // Remove the calling thread's TLS so that it cannot use the slot. + ThreadLocalStorage::Set(tls_index_, NULL); + delete data; +} + +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++) { + wchar_t* name = impl_->thread_name(index); + if (*name != L'\0') + count++; + } + return count; +} + +int StatsTable::GetSlot() const { + StatsTableTLSData* data = GetTLSData(); + if (!data) + return 0; + return data->slot; +} + +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++) { + wchar_t* 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::wstring& 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++) { + wchar_t* row_name = impl_->counter_name(index); + if (!*row_name && !free_slot) + free_slot = index; // save that we found a free slot + else if (!wcsncmp(row_name, name.c_str(), kMaxCounterNameLength)) + return index; + } + return free_slot; +} + +int StatsTable::FindCounter(const std::wstring& 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::AddCounter(const std::wstring& name) { + DCHECK(impl_); + + if (!impl_) + return 0; + + int counter_id = 0; + { + // To add a counter to the shared memory, we need the + // shared memory lock. + SharedMemoryAutoLock lock(&shared_memory_); + + // We have space, so create a new counter. + counter_id = FindCounterOrEmptyRow(name); + if (!counter_id) + return 0; + + std::wstring counter_name = name; + if (name.empty()) + counter_name = kUnknownName; + wcsncpy_s(impl_->counter_name(counter_id), kMaxCounterNameLength, + counter_name.c_str(), _TRUNCATE); + } + + // now add to our in-memory cache + { + AutoLock lock(counters_lock_); + counters_[name] = counter_id; + } + return counter_id; +} + +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 wchar_t* StatsTable::GetRowName(int index) const { + if (!impl_) + return NULL; + + return impl_->counter_name(index); +} + +int StatsTable::GetRowValue(int index, int pid) const { + if (!impl_) + return 0; + + int rv = 0; + int* row = impl_->row(index); + for (int index = 0; index < impl_->max_threads(); index++) { + if (pid == 0 || *impl_->thread_pid(index) == pid) + rv += row[index]; + } + return rv; +} + +int StatsTable::GetRowValue(int index) const { + return GetRowValue(index, 0); +} + +int StatsTable::GetCounterValue(const std::wstring& name, int pid) { + if (!impl_) + return 0; + + int row = FindCounter(name); + if (!row) + return 0; + return GetRowValue(row, pid); +} + +int StatsTable::GetCounterValue(const std::wstring& name) { + return GetCounterValue(name, 0); +} + +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 wchar_t* 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(L""))) + return NULL; + + // Find the counter id for the counter. + std::wstring str_name(name); + int counter = table->FindCounter(str_name); + + // Now we can find the location in the table. + return table->GetLocation(counter, slot); +} diff --git a/base/stats_table.h b/base/stats_table.h new file mode 100644 index 0000000..0938099 --- /dev/null +++ b/base/stats_table.h @@ -0,0 +1,209 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// 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_STATS_TABLE_H__ +#define BASE_STATS_TABLE_H__ + +#include <string> +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/lock.h" +#include "base/shared_memory.h" +#include "base/thread_local_storage.h" + +class StatsTablePrivate; + +namespace { +struct StatsTableTLSData; +} + +class 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::wstring& name, int max_threads, int max_counters); + + // Destroys the StatsTable. When the last StatsTable is destroyed + // (across all processes), the StatsTable is removed from disk. + ~StatsTable(); + + // For convenience, we create a static table. This is generally + // used automatically by the counters. + static StatsTable* current() { return global_table_; } + + // Set the global table for use in this process. + static void set_current(StatsTable* value) { global_table_ = value; } + + // Get the slot id for the calling thread. Returns 0 if no + // slot is assigned. + int GetSlot() const; + + // All threads that contribute data to the table must register with the + // table first. This function will set thread local storage for the + // thread containing the location in the table where this thread will + // write its counter data. + // + // name is just a debugging tag to label the thread, and it does not + // need to be unique. It will be truncated to kMaxThreadNameLength-1 + // characters. + // + // On success, returns the slot id for this thread. On failure, + // returns 0. + int RegisterThread(const std::wstring& name); + + // 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. + void UnregisterThread(); + + // 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::wstring& 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 wchar_t* 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::wstring& 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::wstring& 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 = 32; + + // 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 wchar_t *name); + + private: + // 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::wstring& 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::wstring& name); + + // Get the TLS data for the calling thread. Returns NULL if none is + // initialized. + StatsTableTLSData* GetTLSData() const; + + typedef base::hash_map<std::wstring, int> CountersMap; + + bool opened_; + SharedMemory shared_memory_; + StatsTablePrivate* impl_; + // The counters_lock_ protects the counters_ hash table. + 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_; + TLSSlot tls_index_; + + static StatsTable* global_table_; + DISALLOW_EVIL_CONSTRUCTORS(StatsTable); +}; + +#endif // BASE_STATS_TABLE_H__ diff --git a/base/stats_table_unittest.cc b/base/stats_table_unittest.cc new file mode 100644 index 0000000..28527fc --- /dev/null +++ b/base/stats_table_unittest.cc @@ -0,0 +1,396 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <process.h> +#include <windows.h> + +#include "base/multiprocess_test.h" +#include "base/stats_table.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class StatsTableTest : public MultiProcessTest { + }; +} + +// Open a StatsTable and verify that we can write to each of the +// locations in the table. +TEST_F(StatsTableTest, VerifySlots) { + const std::wstring kTableName = L"VerifySlotsStatTable"; + const int kMaxThreads = 1; + const int kMaxCounter = 5; + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + + // Register a single thread. + std::wstring thread_name = L"mainThread"; + int slot_id = table.RegisterThread(thread_name); + EXPECT_TRUE(slot_id); + + // Fill up the table with counters. + std::wstring counter_base_name = L"counter"; + for (int index=0; index < kMaxCounter; index++) { + std::wstring counter_name = counter_base_name; + StringAppendF(&counter_name, L"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(L"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); +} + +// CounterZero will continually be set to 0. +const std::wstring kCounterZero = L"CounterZero"; +// Counter1313 will continually be set to 1313. +const std::wstring kCounter1313 = L"Counter1313"; +// CounterIncrement will be incremented each time. +const std::wstring kCounterIncrement = L"CounterIncrement"; +// CounterDecrement will be decremented each time. +const std::wstring kCounterDecrement = L"CounterDecrement"; +// CounterMixed will be incremented by odd numbered threads and +// decremented by even threads. +const std::wstring kCounterMixed = L"CounterMixed"; +// The number of thread loops that we will do. +const int kThreadLoops = 1000; + +unsigned __stdcall StatsTableMultipleThreadMain(void* param) { + // Each thread will open the shared memory and set counters + // concurrently in a loop. We'll use some pauses to + // mixup the thread scheduling. + int16 id = reinterpret_cast<int16>(param); + + 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(); + Sleep(index % 10); // short wait + } + return 0; +} +// Create a few threads and have them poke on their counters. +TEST_F(StatsTableTest, MultipleThreads) { + // Create a stats table. + const std::wstring kTableName = L"MultipleThreadStatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + 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. + HANDLE threads[kMaxThreads]; + + // Spawn the threads. + for (int16 index = 0; index < kMaxThreads; index++) { + void* argument = reinterpret_cast<void*>(index); + unsigned thread_id; + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, StatsTableMultipleThreadMain, argument, 0, + &thread_id)); + EXPECT_NE((HANDLE)NULL, threads[index]); + } + + // Wait for the threads to finish. + for (int index = 0; index < kMaxThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], 60 * 1000); + EXPECT_EQ(rv, WAIT_OBJECT_0); // verify all threads finished + } + 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::wstring name; + name = L"c:" + kCounterZero; + EXPECT_EQ(0, table.GetCounterValue(name)); + name = L"c:" + kCounter1313; + EXPECT_EQ(1313 * kMaxThreads, + table.GetCounterValue(name)); + name = L"c:" + kCounterIncrement; + EXPECT_EQ(kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + name = L"c:" + kCounterDecrement; + EXPECT_EQ(-kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + name = L"c:" + kCounterMixed; + EXPECT_EQ((kMaxThreads % 2) * kThreadLoops, + table.GetCounterValue(name)); + EXPECT_EQ(0, table.CountThreadsRegistered()); +} + +const std::wstring kTableName = L"MultipleProcessStatTable"; + +extern "C" int __declspec(dllexport) ChildProcessMain() { + // 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(kTableName, 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(); + Sleep(index % 10); // short wait + } + return 0; +} + +// Create a few threads and have them poke on their counters. +TEST_F(StatsTableTest, MultipleProcesses) { + // Create a stats table. + const std::wstring kTableName = L"MultipleProcessStatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + 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. + HANDLE threads[kMaxThreads]; + + // Spawn the processes. + for (int16 index = 0; index < kMaxThreads; index++) { + threads[index] = this->SpawnChild(L"ChildProcessMain"); + EXPECT_NE((HANDLE)NULL, threads[index]); + } + + // Wait for the threads to finish. + for (int index = 0; index < kMaxThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], 60 * 1000); + EXPECT_EQ(rv, WAIT_OBJECT_0); // verify all threads finished + } + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + + // Verify the various counters are correct. + std::wstring name; + name = L"c:" + kCounterZero; + EXPECT_EQ(0, table.GetCounterValue(name)); + name = L"c:" + kCounter1313; + EXPECT_EQ(1313 * kMaxThreads, + table.GetCounterValue(name)); + name = L"c:" + kCounterIncrement; + EXPECT_EQ(kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + name = L"c:" + kCounterDecrement; + EXPECT_EQ(-kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + EXPECT_EQ(0, table.CountThreadsRegistered()); +} + +class MockStatsCounter : public StatsCounter { + public: + MockStatsCounter(const std::wstring& name) + : StatsCounter(name) {} + int* Pointer() { return GetPtr(); } +}; + +// Test some basic StatsCounter operations +TEST_F(StatsTableTest, StatsCounter) { + // Create a stats table. + const std::wstring kTableName = L"StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + MockStatsCounter foo(L"foo"); + + // Test initial state. + EXPECT_TRUE(foo.Enabled()); + EXPECT_NE(foo.Pointer(), static_cast<int*>(0)); + EXPECT_EQ(0, table.GetCounterValue(L"c:foo")); + EXPECT_EQ(0, *(foo.Pointer())); + + // Test Increment. + while(*(foo.Pointer()) < 123) foo.Increment(); + EXPECT_EQ(123, table.GetCounterValue(L"c:foo")); + foo.Add(0); + EXPECT_EQ(123, table.GetCounterValue(L"c:foo")); + foo.Add(-1); + EXPECT_EQ(122, table.GetCounterValue(L"c:foo")); + + // Test Set. + foo.Set(0); + EXPECT_EQ(0, table.GetCounterValue(L"c:foo")); + foo.Set(100); + EXPECT_EQ(100, table.GetCounterValue(L"c:foo")); + foo.Set(-1); + EXPECT_EQ(-1, table.GetCounterValue(L"c:foo")); + foo.Set(0); + EXPECT_EQ(0, table.GetCounterValue(L"c:foo")); + + // Test Decrement. + foo.Decrement(1); + EXPECT_EQ(-1, table.GetCounterValue(L"c:foo")); + foo.Decrement(0); + EXPECT_EQ(-1, table.GetCounterValue(L"c:foo")); + foo.Decrement(-1); + EXPECT_EQ(0, table.GetCounterValue(L"c:foo")); +} + +class MockStatsCounterTimer : public StatsCounterTimer { + public: + MockStatsCounterTimer(const std::wstring& 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::wstring kTableName = L"StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + MockStatsCounterTimer bar(L"bar"); + + // Test initial state. + EXPECT_FALSE(bar.Running()); + EXPECT_TRUE(bar.start_time().is_null()); + EXPECT_TRUE(bar.stop_time().is_null()); + + // Do some timing. + bar.Start(); + Sleep(500); + bar.Stop(); + EXPECT_LE(500, table.GetCounterValue(L"t:bar")); + + // Verify that timing again is additive. + bar.Start(); + Sleep(500); + bar.Stop(); + EXPECT_LE(1000, table.GetCounterValue(L"t:bar")); +} + +// Test some basic StatsRate operations +TEST_F(StatsTableTest, StatsRate) { + // Create a stats table. + const std::wstring kTableName = L"StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + StatsRate baz(L"baz"); + + // Test initial state. + EXPECT_FALSE(baz.Running()); + EXPECT_EQ(0, table.GetCounterValue(L"c:baz")); + EXPECT_EQ(0, table.GetCounterValue(L"t:baz")); + + // Do some timing. + baz.Start(); + Sleep(500); + baz.Stop(); + EXPECT_EQ(1, table.GetCounterValue(L"c:baz")); + EXPECT_LE(500, table.GetCounterValue(L"t:baz")); + + // Verify that timing again is additive. + baz.Start(); + Sleep(500); + baz.Stop(); + EXPECT_EQ(2, table.GetCounterValue(L"c:baz")); + EXPECT_LE(1000, table.GetCounterValue(L"t:baz")); +} + +// Test some basic StatsScope operations +TEST_F(StatsTableTest, StatsScope) { + // Create a stats table. + const std::wstring kTableName = L"StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + StatsCounterTimer foo(L"foo"); + StatsRate bar(L"bar"); + + // Test initial state. + EXPECT_EQ(0, table.GetCounterValue(L"t:foo")); + EXPECT_EQ(0, table.GetCounterValue(L"t:bar")); + EXPECT_EQ(0, table.GetCounterValue(L"c:bar")); + + // Try a scope. + { + StatsScope<StatsCounterTimer> timer(foo); + StatsScope<StatsRate> timer2(bar); + Sleep(500); + } + EXPECT_LE(500, table.GetCounterValue(L"t:foo")); + EXPECT_LE(500, table.GetCounterValue(L"t:bar")); + EXPECT_EQ(1, table.GetCounterValue(L"c:bar")); + + // Try a second scope. + { + StatsScope<StatsCounterTimer> timer(foo); + StatsScope<StatsRate> timer2(bar); + Sleep(500); + } + EXPECT_LE(1000, table.GetCounterValue(L"t:foo")); + EXPECT_LE(1000, table.GetCounterValue(L"t:bar")); + EXPECT_EQ(2, table.GetCounterValue(L"c:bar")); +}
\ No newline at end of file diff --git a/base/string16.h b/base/string16.h new file mode 100644 index 0000000..2466028 --- /dev/null +++ b/base/string16.h @@ -0,0 +1,217 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// 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 Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE 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. +// +// WHAT: +// A version of std::basic_string that works even on Linux when 2-byte wchar_t +// values (-fshort-wchar) are used. You can access this class as std::string16. +// We also define char16, which std::string16 is based upon. +// +// WHY: +// Firefox uses 2-byte wide characters (UTF-16). On Windows, this is +// mostly compatible with wchar_t, which is 2 bytes (UCS2). +// +// On Linux, sizeof(wchar_t) is 4 bytes by default. We can make it 2 bytes +// using the GCC flag -fshort-wchar. But then std::wstring fails at run time, +// because it calls some functions (like wcslen) that come from glibc -- which +// was built with a 4-byte wchar_t! +// +// So we define std::string16, which is similar to std::wstring but replaces +// all glibc functions with custom, 2-byte-char compatible routines. Fortuntely +// for us, std::wstring uses mostly *inline* wchar_t-based functions (like +// wmemcmp) that are defined in .h files and do not need to be overridden. + +#ifndef BASE_STRING16_H__ +#define BASE_STRING16_H__ + +#include <string> + +#ifdef WIN32 + +typedef wchar_t char16; + +namespace std { + typedef wstring string16; +} + +#else + +typedef unsigned short char16; + +namespace std { + typedef basic_string<char16> string16; +} + + +// Define char16 versions of functions required below in char_traits<char16> +extern "C" { + + inline char16 *char16_wmemmove(char16 *s1, const char16 *s2, size_t n) { + return (char16 *)memmove(s1, s2, n * sizeof(char16)); + } + + inline char16 *char16_wmemcpy(char16 *s1, const char16 *s2, size_t n) { + return (char16 *)memcpy(s1, s2, n * sizeof(char16)); + } + + inline int char16_wmemcmp(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; --n; + } + return 0; + } + + inline const char16 *char16_wmemchr(const char16 *s, char16 c, size_t n) { + while (n > 0) { + if (*s == c) { + return s; + } + ++s; --n; + } + return 0; + } + + inline char16 *char16_wmemset(char16 *s, char16 c, size_t n) { + char16 *s_orig = s; + while (n > 0) { + *s = c; + ++s; --n; + } + return s_orig; + } + + inline size_t char16_wcslen(const char16 *s) { + const char16 *s_orig = s; + while (*s) { ++s; } + return (s - s_orig); + } + +} // END: extern "C" + + +// Definition of char_traits<char16>, which enables basic_string<char16> +// +// This is a slightly modified version of char_traits<wchar_t> from gcc 3.2.2 +namespace std { + + template<> + struct char_traits<char16> + { + typedef char16 char_type; + typedef wint_t int_type; + typedef streamoff off_type; + typedef wstreampos pos_type; + typedef mbstate_t state_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 char16_wmemcmp(__s1, __s2, __n); } + + static size_t + length(const char_type* __s) + { return char16_wcslen(__s); } + + static const char_type* + find(const char_type* __s, size_t __n, const char_type& __a) + { return char16_wmemchr(__s, __a, __n); } + + static char_type* + move(char_type* __s1, const char_type* __s2, int_type __n) + { return char16_wmemmove(__s1, __s2, __n); } + + static char_type* + copy(char_type* __s1, const char_type* __s2, size_t __n) + { return char16_wmemcpy(__s1, __s2, __n); } + + static char_type* + assign(char_type* __s, size_t __n, char_type __a) + { return char16_wmemset(__s, __a, __n); } + + static char_type + to_char_type(const int_type& __c) { return char_type(__c); } + + static int_type + to_int_type(const char_type& __c) { return int_type(__c); } + + static bool + eq_int_type(const int_type& __c1, const int_type& __c2) + { return __c1 == __c2; } + + static int_type + eof() { return static_cast<int_type>(WEOF); } + + static int_type + not_eof(const int_type& __c) + { return eq_int_type(__c, eof()) ? 0 : __c; } + }; + +} // END: namespace std + +#endif // END: WIN32 + +#endif // END: BASE_STRING16_H__ diff --git a/base/string_escape.cc b/base/string_escape.cc new file mode 100644 index 0000000..018531e --- /dev/null +++ b/base/string_escape.cc @@ -0,0 +1,122 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/string_escape.h" + +#include <string> + +#include "base/string_util.h" + +namespace string_escape { + +// Try to escape |c| as a "SingleEscapeCharacter" (\n, etc). If successful, +// returns true and appends the escape sequence to |dst|. +template<typename CHAR> +static bool JavascriptSingleEscapeChar(const CHAR c, std::string* dst) { + 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 '\v': + dst->append("\\v"); + break; + case '\\': + dst->append("\\\\"); + break; + case '"': + dst->append("\\\""); + break; + default: + return false; + } + return true; +} + +void JavascriptDoubleQuote(const std::wstring& str, + bool put_in_quotes, + std::string* dst) { + if (put_in_quotes) + dst->push_back('"'); + + for (std::wstring::const_iterator it = str.begin(); it != str.end(); ++it) { + wchar_t c = *it; + if (!JavascriptSingleEscapeChar(c, dst)) { + if (c > 255) { + // Non-ascii values need to be unicode dst-> + // TODO(tc): Some unicode values are handled specially. See + // spidermonkey code. + StringAppendF(dst, "\\u%04X", c); + } else if (c < 32 || c > 126) { + // Spidermonkey hex escapes these values. + StringAppendF(dst, "\\x%02X", c); + } else { + dst->push_back(static_cast<char>(c)); + } + } + } + + if (put_in_quotes) + dst->push_back('"'); +} + +void JavascriptDoubleQuote(const std::string& str, + bool put_in_quotes, + std::string* dst) { + if (put_in_quotes) + dst->push_back('"'); + + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) { + unsigned char c = *it; + if (!JavascriptSingleEscapeChar(c, dst)) { + // Hex encode if the character is non-printable 7bit ascii + if (c < 32 || c == 127) { + StringAppendF(dst, "\\x%02X", c); + } else { + dst->push_back(static_cast<char>(c)); + } + } + } + + if (put_in_quotes) + dst->push_back('"'); +} + +} // namespace string_escape diff --git a/base/string_escape.h b/base/string_escape.h new file mode 100644 index 0000000..a78ebe95 --- /dev/null +++ b/base/string_escape.h @@ -0,0 +1,60 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This file defines utility functions for escaping strings. + +#ifndef BASE_STRING_ESCAPE_H__ +#define BASE_STRING_ESCAPE_H__ + +#include <string> + +namespace string_escape { + +// Escape |str| appropriately for a javascript string litereal, _appending_ the +// result to |dst|. This will create standard escape sequences (\b, \n), +// hex escape sequences (\x00), and 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|. +void JavascriptDoubleQuote(const std::wstring& str, + bool put_in_quotes, + std::string* dst); + +// Similar to the wide version, but for narrow strings. It will not use +// \uXXXX unicode escape sequences. It will pass non-7bit characters directly +// into the string unencoded, allowing the browser to interpret the encoding. +// The outputted literal, when interpreted by the browser, could result in a +// javascript string of a different length than the input |str|. +void JavascriptDoubleQuote(const std::string& str, + bool put_in_quotes, + std::string* dst); + +} // namespace string_escape + +#endif // BASE_STRING_ESCAPE_H__ diff --git a/base/string_escape_unittest.cc b/base/string_escape_unittest.cc new file mode 100644 index 0000000..91f15b7 --- /dev/null +++ b/base/string_escape_unittest.cc @@ -0,0 +1,79 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/string_escape.h" + +TEST(StringEscapeTest, JavascriptDoubleQuote) { + static const char* kToEscape = "\b\001aZ\"\\wee"; + static const char* kEscaped = "\\b\\x01aZ\\\"\\\\wee"; + static const char* kEscapedQuoted = "\"\\b\\x01aZ\\\"\\\\wee\""; + static const wchar_t* kUToEscape = L"\b\x0001" L"a\x123fZ\"\\wee"; + static const char* kUEscaped = "\\b\\x01a\\u123FZ\\\"\\\\wee"; + static const char* kUEscapedQuoted = "\"\\b\\x01a\\u123FZ\\\"\\\\wee\""; + + std::string out; + + // Test wide unicode escaping + out = "testy: "; + string_escape::JavascriptDoubleQuote(std::wstring(kUToEscape), false, &out); + ASSERT_EQ(std::string("testy: ") + kUEscaped, out); + + out = "testy: "; + string_escape::JavascriptDoubleQuote(std::wstring(kUToEscape), true, &out); + ASSERT_EQ(std::string("testy: ") + kUEscapedQuoted, out); + + // Test null and high bit / negative unicode values + std::wstring wstr(L"TeSt"); + wstr.push_back(0); + wstr.push_back(0xffb1); + wstr.push_back(0x00ff); + + out = "testy: "; + string_escape::JavascriptDoubleQuote(wstr, false, &out); + ASSERT_EQ("testy: TeSt\\x00\\uFFB1\\xFF", out); + + // Test escaping of 7bit ascii + out = "testy: "; + string_escape::JavascriptDoubleQuote(std::string(kToEscape), false, &out); + ASSERT_EQ(std::string("testy: ") + kEscaped, out); + + // Test null, non-printable, and non-7bit + std::string str("TeSt"); + str.push_back(0); + str.push_back(15); + str.push_back(127); + str.push_back(-16); + str.push_back(-128); + str.push_back('!'); + + out = "testy: "; + string_escape::JavascriptDoubleQuote(str, false, &out); + ASSERT_EQ("testy: TeSt\\x00\\x0F\\x7F\xf0\x80!", out); +} diff --git a/base/string_piece.cc b/base/string_piece.cc new file mode 100644 index 0000000..e2c0aa7 --- /dev/null +++ b/base/string_piece.cc @@ -0,0 +1,240 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// Copied from strings/stringpiece.cc with modifications + +#include <algorithm> +#include <iostream> + +#include "base/string_piece.h" + +typedef StringPiece::size_type size_type; + +std::ostream& operator<<(std::ostream& o, const StringPiece& piece) { + o.write(piece.data(), static_cast<std::streamsize>(piece.size())); + return o; +} + +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; +} + +void StringPiece::CopyToString(std::string* target) const { + target->assign(!empty() ? data() : "", size()); +} + +void StringPiece::AppendToString(std::string* target) const { + if (!empty()) + target->append(data(), size()); +} + +size_type StringPiece::copy(char* buf, size_type n, size_type pos) const { + size_type ret = std::min(length_ - pos, n); + memcpy(buf, ptr_ + pos, ret); + return ret; +} + +size_type StringPiece::find(const StringPiece& s, size_type pos) const { + if (pos > length_) + return npos; + + const char* result = std::search(ptr_ + pos, ptr_ + length_, + s.ptr_, s.ptr_ + s.length_); + const size_type xpos = result - ptr_; + return xpos + s.length_ <= length_ ? xpos : npos; +} + +size_type StringPiece::find(char c, size_type pos) const { + if (pos >= length_) + return npos; + + const char* result = std::find(ptr_ + pos, ptr_ + length_, c); + return result != ptr_ + length_ ? result - ptr_ : npos; +} + +size_type StringPiece::rfind(const StringPiece& s, size_type pos) const { + if (length_ < s.length_) + return npos; + + if (s.empty()) + return std::min(length_, pos); + + const char* last = ptr_ + std::min(length_ - s.length_, pos) + s.length_; + const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_); + return result != last ? result - ptr_ : npos; +} + +size_type StringPiece::rfind(char c, size_type pos) const { + if (length_ == 0) + return npos; + + for (size_type i = std::min(pos, length_ - 1); ; --i) { + if (ptr_[i] == c) + return i; + if (i == 0) + break; + } + return 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 size_type length = characters_wanted.length(); + const char* const data = characters_wanted.data(); + for (size_type i = 0; i < length; ++i) { + table[static_cast<unsigned char>(data[i])] = true; + } +} + +size_type StringPiece::find_first_of(const StringPiece& s, + size_type pos) const { + if (length_ == 0 || s.length_ == 0) + return npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) + return find_first_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (size_type i = pos; i < length_; ++i) { + if (lookup[static_cast<unsigned char>(ptr_[i])]) { + return i; + } + } + return npos; +} + +size_type StringPiece::find_first_not_of(const StringPiece& s, + size_type pos) const { + if (length_ == 0) + return npos; + + if (s.length_ == 0) + return 0; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) + return find_first_not_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (size_type i = pos; i < length_; ++i) { + if (!lookup[static_cast<unsigned char>(ptr_[i])]) { + return i; + } + } + return npos; +} + +size_type StringPiece::find_first_not_of(char c, size_type pos) const { + if (length_ == 0) + return npos; + + for (; pos < length_; ++pos) { + if (ptr_[pos] != c) { + return pos; + } + } + return npos; +} + +size_type StringPiece::find_last_of(const StringPiece& s, size_type pos) const { + if (length_ == 0 || s.length_ == 0) + return npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) + return find_last_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (size_type i = std::min(pos, length_ - 1); ; --i) { + if (lookup[static_cast<unsigned char>(ptr_[i])]) + return i; + if (i == 0) + break; + } + return npos; +} + +size_type StringPiece::find_last_not_of(const StringPiece& s, + size_type pos) const { + if (length_ == 0) + return npos; + + size_type i = std::min(pos, length_ - 1); + if (s.length_ == 0) + return i; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.length_ == 1) + return find_last_not_of(s.ptr_[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (; ; --i) { + if (!lookup[static_cast<unsigned char>(ptr_[i])]) + return i; + if (i == 0) + break; + } + return npos; +} + +size_type StringPiece::find_last_not_of(char c, size_type pos) const { + if (length_ == 0) + return npos; + + for (size_type i = std::min(pos, length_ - 1); ; --i) { + if (ptr_[i] != c) + return i; + if (i == 0) + break; + } + return npos; +} + +StringPiece StringPiece::substr(size_type pos, size_type n) const { + if (pos > length_) pos = length_; + if (n > length_ - pos) n = length_ - pos; + return StringPiece(ptr_ + pos, n); +} + +const StringPiece::size_type StringPiece::npos = size_type(-1); diff --git a/base/string_piece.h b/base/string_piece.h new file mode 100644 index 0000000..8cb375e --- /dev/null +++ b/base/string_piece.h @@ -0,0 +1,209 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// 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. +// + +#ifndef BASE_STRING_PIECE_H__ +#define BASE_STRING_PIECE_H__ + +#include <algorithm> +#include <iosfwd> +#include <string> + +#include "base/basictypes.h" + +class StringPiece { + public: + typedef size_t size_type; + + private: + const char* ptr_; + size_type length_; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected. + StringPiece() : ptr_(NULL), length_(0) { } + StringPiece(const char* str) + : ptr_(str), length_((str == NULL) ? 0 : strlen(str)) { } + StringPiece(const std::string& str) + : ptr_(str.data()), length_(str.size()) { } + StringPiece(const char* offset, size_type len) + : ptr_(offset), length_(len) { } + + // 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 char* 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 char* data, size_type len) { ptr_ = data; length_ = len; } + void set(const char* str) { + ptr_ = str; + length_ = str ? strlen(str) : 0; + } + void set(const void* data, size_type len) { + ptr_ = reinterpret_cast<const char*>(data); + length_ = len; + } + + char 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 StringPiece& x) const { + int r = wordmemcmp(ptr_, x.ptr_, std::min(length_, x.length_)); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; + } + + std::string as_string() const { + // std::string doesn't like to take a NULL pointer even with a 0 size. + return std::string(!empty() ? data() : "", size()); + } + + void CopyToString(std::string* target) const; + void AppendToString(std::string* target) const; + + // Does "this" start with "x" + bool starts_with(const StringPiece& x) const { + return ((length_ >= x.length_) && + (wordmemcmp(ptr_, x.ptr_, x.length_) == 0)); + } + + // Does "this" end with "x" + bool ends_with(const StringPiece& x) const { + return ((length_ >= x.length_) && + (wordmemcmp(ptr_ + (length_-x.length_), x.ptr_, x.length_) == 0)); + } + + // standard STL container boilerplate + typedef char value_type; + typedef const char* pointer; + typedef const char& reference; + typedef const char& const_reference; + typedef ptrdiff_t difference_type; + static const size_type npos; + typedef const char* const_iterator; + typedef const char* iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + typedef std::reverse_iterator<iterator> reverse_iterator; + iterator begin() const { return ptr_; } + 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_; } + + size_type copy(char* buf, size_type n, size_type pos = 0) const; + + size_type find(const StringPiece& s, size_type pos = 0) const; + size_type find(char c, size_type pos = 0) const; + size_type rfind(const StringPiece& s, size_type pos = npos) const; + size_type rfind(char c, size_type pos = npos) const; + + size_type find_first_of(const StringPiece& s, size_type pos = 0) const; + size_type find_first_of(char c, size_type pos = 0) const { + return find(c, pos); + } + size_type find_first_not_of(const StringPiece& s, size_type pos = 0) const; + size_type find_first_not_of(char c, size_type pos = 0) const; + size_type find_last_of(const StringPiece& s, size_type pos = npos) const; + size_type find_last_of(char c, size_type pos = npos) const { + return rfind(c, pos); + } + size_type find_last_not_of(const StringPiece& s, size_type pos = npos) const; + size_type find_last_not_of(char c, size_type pos = npos) const; + + StringPiece substr(size_type pos, size_type n = npos) const; + + static int wordmemcmp(const char* p, const char* p2, size_type N) { + return memcmp(p, p2, N); + } +}; + +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(), + std::min(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); +} + +// allow StringPiece to be logged (needed for unit testing). +extern std::ostream& operator<<(std::ostream& o, const StringPiece& piece); + +#endif // BASE_STRING_PIECE_H__ diff --git a/base/string_piece_unittest.cc b/base/string_piece_unittest.cc new file mode 100644 index 0000000..af8f6f4 --- /dev/null +++ b/base/string_piece_unittest.cc @@ -0,0 +1,565 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string> + +#include "base/string_piece.h" + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(StringPieceTest, CheckComparisonOperators) { +#define CMP_Y(op, x, y) \ + ASSERT_TRUE( (StringPiece((x)) op StringPiece((y)))); \ + ASSERT_TRUE( (StringPiece((x)).compare(StringPiece((y))) op 0)) + +#define CMP_N(op, x, y) \ + ASSERT_FALSE(StringPiece((x)) op StringPiece((y))); \ + ASSERT_FALSE(StringPiece((x)).compare(StringPiece((y))) 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 +} + +TEST(StringPieceTest, CheckSTL) { + StringPiece a("abcdefghijklmnopqrstuvwxyz"); + StringPiece b("abc"); + StringPiece c("xyz"); + StringPiece d("foobar"); + StringPiece e; + std::string temp("123"); + temp += '\0'; + temp += "456"; + StringPiece f(temp); + + ASSERT_EQ(a[6], 'g'); + ASSERT_EQ(b[0], 'a'); + ASSERT_EQ(c[2], 'z'); + ASSERT_EQ(f[3], '\0'); + ASSERT_EQ(f[5], '5'); + + ASSERT_EQ(*d.data(), 'f'); + ASSERT_EQ(d.data()[5], 'r'); + ASSERT_TRUE(e.data() == NULL); + + ASSERT_EQ(*a.begin(), 'a'); + ASSERT_EQ(*(b.begin() + 2), 'c'); + ASSERT_EQ(*(c.end() - 1), 'z'); + + ASSERT_EQ(*a.rbegin(), 'z'); + ASSERT_EQ(*(b.rbegin() + 2), 'a'); + ASSERT_EQ(*(c.rend() - 1), 'x'); + ASSERT_TRUE(a.rbegin() + 26 == a.rend()); + + ASSERT_EQ(a.size(), 26); + ASSERT_EQ(b.size(), 3); + ASSERT_EQ(c.size(), 3); + ASSERT_EQ(d.size(), 6); + ASSERT_EQ(e.size(), 0); + ASSERT_EQ(f.size(), 7); + + 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(), 0); + 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()); + + char buf[4] = { '%', '%', '%', '%' }; + ASSERT_EQ(a.copy(buf, 4), 4); + 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), 3); + 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), 3); + 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), 0); + ASSERT_EQ(a.find(b, 1), StringPiece::npos); + ASSERT_EQ(a.find(c), 23); + ASSERT_EQ(a.find(c, 9), 23); + 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), 0); + ASSERT_EQ(a.find(e), 0); + ASSERT_EQ(a.find(d, 12), 12); + ASSERT_EQ(a.find(e, 17), 17); + 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'), 0); + ASSERT_EQ(a.find('c'), 2); + ASSERT_EQ(a.find('z'), 25); + ASSERT_EQ(a.find('$'), StringPiece::npos); + ASSERT_EQ(a.find('\0'), StringPiece::npos); + ASSERT_EQ(f.find('\0'), 3); + ASSERT_EQ(f.find('3'), 2); + ASSERT_EQ(f.find('5'), 5); + ASSERT_EQ(g.find('o'), 4); + ASSERT_EQ(g.find('o', 4), 4); + ASSERT_EQ(g.find('o', 5), 8); + 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), 0); + ASSERT_EQ(a.rfind(b, 1), 0); + ASSERT_EQ(a.rfind(c), 23); + ASSERT_EQ(a.rfind(c, 22), StringPiece::npos); + ASSERT_EQ(a.rfind(c, 1), StringPiece::npos); + ASSERT_EQ(a.rfind(c, 0), StringPiece::npos); + ASSERT_EQ(b.rfind(c), StringPiece::npos); + ASSERT_EQ(b.rfind(c, 0), StringPiece::npos); + ASSERT_EQ(a.rfind(d), a.as_string().rfind(std::string())); + ASSERT_EQ(a.rfind(e), a.as_string().rfind(std::string())); + ASSERT_EQ(a.rfind(d, 12), 12); + ASSERT_EQ(a.rfind(e, 17), 17); + 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'), 8); + ASSERT_EQ(g.rfind('q'), StringPiece::npos); + ASSERT_EQ(g.rfind('o', 8), 8); + ASSERT_EQ(g.rfind('o', 7), 4); + ASSERT_EQ(g.rfind('o', 3), StringPiece::npos); + ASSERT_EQ(f.rfind('\0'), 3); + ASSERT_EQ(f.rfind('\0', 12), 3); + ASSERT_EQ(f.rfind('3'), 2); + ASSERT_EQ(f.rfind('5'), 5); + // 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(a.find_first_of(b), 0); + ASSERT_EQ(a.find_first_of(b, 0), 0); + ASSERT_EQ(a.find_first_of(b, 1), 1); + ASSERT_EQ(a.find_first_of(b, 2), 2); + ASSERT_EQ(a.find_first_of(b, 3), StringPiece::npos); + ASSERT_EQ(a.find_first_of(c), 23); + ASSERT_EQ(a.find_first_of(c, 23), 23); + ASSERT_EQ(a.find_first_of(c, 24), 24); + ASSERT_EQ(a.find_first_of(c, 25), 25); + ASSERT_EQ(a.find_first_of(c, 26), StringPiece::npos); + ASSERT_EQ(g.find_first_of(b), 13); + ASSERT_EQ(g.find_first_of(c), 0); + 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), 3); + ASSERT_EQ(a.find_first_not_of(c), 0); + 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), 0); + ASSERT_EQ(a.find_first_not_of(f), 0); + ASSERT_EQ(a.find_first_not_of(d), 0); + ASSERT_EQ(a.find_first_not_of(e), 0); + // 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'), 0); + ASSERT_EQ(g.find_first_not_of('x'), 2); + ASSERT_EQ(f.find_first_not_of('\0'), 0); + ASSERT_EQ(f.find_first_not_of('\0', 3), 4); + ASSERT_EQ(f.find_first_not_of('\0', 2), 2); + // 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), 2); + ASSERT_EQ(a.find_last_of(c), a.size()-1); + ASSERT_EQ(f.find_last_of(i), 6); + ASSERT_EQ(a.find_last_of('a'), 0); + ASSERT_EQ(a.find_last_of('b'), 1); + ASSERT_EQ(a.find_last_of('z'), 25); + ASSERT_EQ(a.find_last_of('a', 5), 0); + ASSERT_EQ(a.find_last_of('b', 5), 1); + ASSERT_EQ(a.find_last_of('b', 0), StringPiece::npos); + ASSERT_EQ(a.find_last_of('z', 25), 25); + ASSERT_EQ(a.find_last_of('z', 24), StringPiece::npos); + ASSERT_EQ(f.find_last_of(i, 5), 5); + ASSERT_EQ(f.find_last_of(i, 6), 6); + 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), 22); + 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), 4); + ASSERT_EQ(a.find_last_not_of(c, 24), 22); + ASSERT_EQ(a.find_last_not_of(b, 3), 3); + 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), 4); + ASSERT_EQ(f.find_last_not_of(e, 4), 4); + 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'), 1); + ASSERT_EQ(h.find_last_not_of('x', 2), 2); + ASSERT_EQ(h.find_last_not_of('=', 2), StringPiece::npos); + ASSERT_EQ(b.find_last_not_of('b', 1), 0); + // 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); +} + +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(), 6); + ASSERT_EQ(s2, "foobar"); + b.CopyToString(&s2); + ASSERT_EQ(s2.size(), 7); + ASSERT_EQ(s1, s2); + e.CopyToString(&s2); + ASSERT_TRUE(s2.empty()); + + // AppendToString + s2.erase(); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 6); + ASSERT_EQ(s2, "foobar"); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 12); + 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)); + + // remove_prefix + StringPiece c(a); + c.remove_prefix(3); + ASSERT_EQ(c, "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, "foo"); + c = a; + c.remove_suffix(0); + ASSERT_EQ(c, a); + c.remove_suffix(c.size()); + ASSERT_EQ(c, e); + + // set + c.set("foobar", 6); + ASSERT_EQ(c, a); + c.set("foobar", 0); + ASSERT_EQ(c, e); + c.set("foobar", 7); + ASSERT_NE(c, a); + + c.set("foobar"); + ASSERT_EQ(c, a); + + c.set(static_cast<const void*>("foobar"), 6); + ASSERT_EQ(c, a); + c.set(static_cast<const void*>("foobar"), 0); + ASSERT_EQ(c, e); + c.set(static_cast<const void*>("foobar"), 7); + ASSERT_NE(c, a); + + // as_string + std::string s3(a.as_string().c_str(), 7); + ASSERT_EQ(c, s3); + std::string s4(e.as_string()); + ASSERT_TRUE(s4.empty()); +} + +TEST(StringPieceTest, CheckNULL) { + // we used to crash here, but now we don't. + StringPiece s(NULL); + ASSERT_EQ(s.data(), (const char*)NULL); + ASSERT_EQ(s.size(), 0); + + s.set(NULL); + ASSERT_EQ(s.data(), (const char*)NULL); + ASSERT_EQ(s.size(), 0); +} + +TEST(StringPieceTest, CheckComparisons2) { + StringPiece abc("abcdefghijklmnopqrstuvwxyz"); + + // check comparison operations on strings longer than 4 bytes. + ASSERT_TRUE(abc == StringPiece("abcdefghijklmnopqrstuvwxyz")); + ASSERT_TRUE(abc.compare(StringPiece("abcdefghijklmnopqrstuvwxyz")) == 0); + + ASSERT_TRUE(abc < StringPiece("abcdefghijklmnopqrstuvwxzz")); + ASSERT_TRUE(abc.compare(StringPiece("abcdefghijklmnopqrstuvwxzz")) < 0); + + ASSERT_TRUE(abc > StringPiece("abcdefghijklmnopqrstuvwxyy")); + ASSERT_TRUE(abc.compare(StringPiece("abcdefghijklmnopqrstuvwxyy")) > 0); + + // 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")); +} + +TEST(StringPieceTest, StringCompareNotAmbiguous) { + ASSERT_TRUE("hello" == std::string("hello")); + ASSERT_TRUE("hello" < std::string("world")); +} + +TEST(StringPieceTest, HeterogenousStringPieceEquals) { + ASSERT_TRUE(StringPiece("hello") == std::string("hello")); + ASSERT_TRUE("hello" == StringPiece("hello")); +} diff --git a/base/string_tokenizer.h b/base/string_tokenizer.h new file mode 100644 index 0000000..9b0c468 --- /dev/null +++ b/base/string_tokenizer.h @@ -0,0 +1,225 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_STRING_TOKENIZER_H__ +#define BASE_STRING_TOKENIZER_H__ + +#include <string> + +// 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. +// +// +// EXAMPLE 1: +// +// StringTokenizer t("this is a test", " "); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// this +// is +// a +// test +// +// +// EXAMPLE 2: +// +// StringTokenizer t("no-cache=\"foo, bar\", private", ", "); +// t.set_quote_chars("\""); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// no-cache="foo, bar" +// private +// +// +// EXAMPLE 3: +// +// bool next_is_option = false, next_is_value = false; +// std::string input = "text/html; charset=UTF-8; foo=bar"; +// StringTokenizer t(input, "; ="); +// t.set_options(StringTokenizer::RETURN_DELIMS); +// while (t.GetNext()) { +// if (t.token_is_delim()) { +// switch (*t.token_begin()) { +// case ';': +// next_is_option = true; +// break; +// case '=': +// next_is_value = true; +// break; +// } +// } else { +// const char* label; +// if (next_is_option) { +// label = "option-name"; +// next_is_option = false; +// } else if (next_is_value) { +// label = "option-value"; +// next_is_value = false; +// } else { +// label = "mime-type"; +// } +// printf("%s: %s\n", label, t.token().c_str()); +// } +// } +// +// +template <class str> +class StringTokenizerT { + public: + typedef typename str::const_iterator const_iterator; + 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, + }; + + 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 std::string& 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() { + 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 delim + } + while (token_end_ != end_ && AdvanceOne(&state, *token_end_)) + ++token_end_; + return true; + } + + // 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_); } + + private: + void Init(const_iterator string_begin, + const_iterator string_end, + const str& delims) { + token_end_ = string_begin; + end_ = string_end; + delims_ = delims; + options_ = 0; + } + + 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) {} + }; + + // 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 token_begin_; + const_iterator token_end_; + const_iterator end_; + str delims_; + str quotes_; + int options_; + bool token_is_delim_; +}; + +typedef StringTokenizerT<std::string> StringTokenizer; +typedef StringTokenizerT<std::wstring> WStringTokenizer; + +#endif // BASE_STRING_TOKENIZER_H__ diff --git a/base/string_tokenizer_unittest.cc b/base/string_tokenizer_unittest.cc new file mode 100644 index 0000000..16b8018 --- /dev/null +++ b/base/string_tokenizer_unittest.cc @@ -0,0 +1,232 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/string_tokenizer.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace { +class StringTokenizerTest : public testing::Test {}; +} + +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, 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()); +} diff --git a/base/string_util.cc b/base/string_util.cc new file mode 100644 index 0000000..2122b9f --- /dev/null +++ b/base/string_util.cc @@ -0,0 +1,1021 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// StringPrintf stuff based on strings/stringprintf.cc by Sanjay Ghemawat + +#include "base/string_util.h" + +#include <algorithm> +#include <math.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/singleton.h" + +namespace { + +// Hack to convert any char-like type to its unsigned counterpart. +// For example, it will convert char, signed char and unsigned char to unsigned +// char. +template<typename T> +struct ToUnsigned { + typedef T Unsigned; +}; + +template<> +struct ToUnsigned<char> { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned<signed char> { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned<wchar_t> { + typedef unsigned short Unsigned; +}; +template<> +struct ToUnsigned<short> { + typedef unsigned short Unsigned; +}; + +// Used by ReplaceStringPlaceholders to track the position in the string of +// replaced parameters. +struct ReplacementOffset { + ReplacementOffset(int parameter, size_t offset) + : parameter(parameter), + offset(offset) {} + + // Index of the parameter. + int parameter; + + // Starting position in the string. + size_t offset; +}; + +static bool CompareParameter(const ReplacementOffset& elem1, + const ReplacementOffset& elem2) { + return elem1.parameter < elem2.parameter; +} + +} // namespace + + +const std::string& EmptyString() { + return *Singleton<std::string>::get(); +} + +const std::wstring& EmptyWString() { + return *Singleton<std::wstring>::get(); +} + +const wchar_t kWhitespaceWide[] = { + 0x0009, // <control-0009> to <control-000D> + 0x000A, + 0x000B, + 0x000C, + 0x000D, + 0x0020, // Space + 0x0085, // <control-0085> + 0x00A0, // No-Break Space + 0x1680, // Ogham Space Mark + 0x180E, // Mongolian Vowel Separator + 0x2000, // En Quad to Hair Space + 0x2001, + 0x2002, + 0x2003, + 0x2004, + 0x2005, + 0x2006, + 0x2007, + 0x2008, + 0x2009, + 0x200A, + 0x200C, // Zero Width Non-Joiner + 0x2028, // Line Separator + 0x2029, // Paragraph Separator + 0x202F, // Narrow No-Break Space + 0x205F, // Medium Mathematical Space + 0x3000, // Ideographic Space + 0 +}; +const char kWhitespaceASCII[] = { + 0x09, // <control-0009> to <control-000D> + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x20, // Space + '\x85', // <control-0085> + '\xa0', // No-Break Space + 0 +}; +const char* const kCodepageUTF8 = "UTF-8"; + +template<typename STR> +TrimPositions TrimStringT(const STR& input, + const typename STR::value_type trim_chars[], + TrimPositions positions, + STR* output) { + // Find the edges of leading/trailing whitespace as desired. + const typename STR::size_type last_char = input.length() - 1; + const typename STR::size_type first_good_char = (positions & TRIM_LEADING) ? + input.find_first_not_of(trim_chars) : 0; + const typename STR::size_type last_good_char = (positions & TRIM_TRAILING) ? + input.find_last_not_of(trim_chars) : last_char; + + // When the string was all whitespace, report that we stripped off whitespace + // from whichever position the caller was interested in. For empty input, we + // stripped no whitespace, but we still need to clear |output|. + if (input.empty() || + (first_good_char == STR::npos) || (last_good_char == STR::npos)) { + bool input_was_empty = input.empty(); // in case output == &input + output->clear(); + return input_was_empty ? TRIM_NONE : positions; + } + + // Trim the whitespace. + *output = + input.substr(first_good_char, last_good_char - first_good_char + 1); + + // Return where we trimmed from. + return static_cast<TrimPositions>( + ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) | + ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING)); +} + +bool TrimString(const std::wstring& input, + wchar_t trim_chars[], + std::wstring* output) { + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; +} + +bool TrimString(const std::string& input, + char trim_chars[], + std::string* output) { + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; +} + +TrimPositions TrimWhitespace(const std::wstring& input, + TrimPositions positions, + std::wstring* output) { + return TrimStringT(input, kWhitespaceWide, positions, output); +} + +TrimPositions TrimWhitespace(const std::string& input, + TrimPositions positions, + std::string* output) { + return TrimStringT(input, kWhitespaceASCII, positions, output); +} + +std::wstring CollapseWhitespace(const std::wstring& text, + bool trim_sequences_with_line_breaks) { + std::wstring 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 (std::wstring::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::string WideToASCII(const std::wstring& wide) { + DCHECK(IsStringASCII(wide)); + return std::string(wide.begin(), wide.end()); +} + +std::wstring ASCIIToWide(const std::string& ascii) { + DCHECK(IsStringASCII(ascii)); + return std::wstring(ascii.begin(), ascii.end()); +} + +// Latin1 is just the low range of Unicode, so we can copy directly to convert. +bool WideToLatin1(const std::wstring& wide, std::string* latin1) { + std::string output; + output.resize(wide.size()); + latin1->clear(); + for (size_t i = 0; i < wide.size(); i++) { + if (wide[i] > 255) + return false; + output[i] = static_cast<char>(wide[i]); + } + latin1->swap(output); + return true; +} + +bool IsString8Bit(const std::wstring& str) { + for (size_t i = 0; i < str.length(); i++) { + if (str[i] > 255) + return false; + } + return true; +} + +bool IsStringASCII(const std::wstring& str) { + for (size_t i = 0; i < str.length(); i++) { + if (str[i] > 0x7F) + return false; + } + return true; +} + +bool IsStringASCII(const std::string& str) { + for (size_t i = 0; i < str.length(); i++) { + if (static_cast<unsigned char>(str[i]) > 0x7F) + return false; + } + return true; +} + +// Helper functions that determine whether the given character begins a +// UTF-8 sequence of bytes with the given length. A character satisfies +// "IsInUTF8Sequence" if it is anything but the first byte in a multi-byte +// character. +static inline bool IsBegin2ByteUTF8(int c) { + return (c & 0xE0) == 0xC0; +} +static inline bool IsBegin3ByteUTF8(int c) { + return (c & 0xF0) == 0xE0; +} +static inline bool IsBegin4ByteUTF8(int c) { + return (c & 0xF8) == 0xF0; +} +static inline bool IsInUTF8Sequence(int c) { + return (c & 0xC0) == 0x80; +} + +// This function was copied from Mozilla, with modifications. The original code +// was 'IsUTF8' in xpcom/string/src/nsReadableUtils.cpp. The license block for +// this function is: +// This function subject to the Mozilla Public License Version +// 1.1 (the "License"); you may not use this code except in compliance with +// the License. You may obtain a copy of the License at +// http://www.mozilla.org/MPL/ +// +// Software distributed under the License is distributed on an "AS IS" basis, +// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +// for the specific language governing rights and limitations under the +// License. +// +// The Original Code is mozilla.org code. +// +// The Initial Developer of the Original Code is +// Netscape Communications Corporation. +// Portions created by the Initial Developer are Copyright (C) 2000 +// the Initial Developer. All Rights Reserved. +// +// Contributor(s): +// Scott Collins <scc@mozilla.org> (original author) +// +// This is a template so that it can be run on wide and 8-bit strings. We want +// to run it on wide strings when we have input that we think may have +// originally been UTF-8, but has been converted to wide characters because +// that's what we (and Windows) use internally. +template<typename CHAR> +static bool IsStringUTF8T(const CHAR* str) { + bool overlong = false; + bool surrogate = false; + bool nonchar = false; + + // overlong byte upper bound + typename ToUnsigned<CHAR>::Unsigned olupper = 0; + + // surrogate byte lower bound + typename ToUnsigned<CHAR>::Unsigned slower = 0; + + // incremented when inside a multi-byte char to indicate how many bytes + // are left in the sequence + int positions_left = 0; + + for (int i = 0; str[i] != 0; i++) { + // This whole function assume an unsigned value so force its conversion to + // an unsigned value. + typename ToUnsigned<CHAR>::Unsigned c = str[i]; + if (c < 0x80) + continue; // ASCII + + if (c <= 0xC1) { + // [80-BF] where not expected, [C0-C1] for overlong + return false; + } else if (IsBegin2ByteUTF8(c)) { + positions_left = 1; + } else if (IsBegin3ByteUTF8(c)) { + positions_left = 2; + if (c == 0xE0) { + // to exclude E0[80-9F][80-BF] + overlong = true; + olupper = 0x9F; + } else if (c == 0xED) { + // ED[A0-BF][80-BF]: surrogate codepoint + surrogate = true; + slower = 0xA0; + } else if (c == 0xEF) { + // EF BF [BE-BF] : non-character + nonchar = true; + } + } else if (c <= 0xF4) { + positions_left = 3; + nonchar = true; + if (c == 0xF0) { + // to exclude F0[80-8F][80-BF]{2} + overlong = true; + olupper = 0x8F; + } else if (c == 0xF4) { + // to exclude F4[90-BF][80-BF] + // actually not surrogates but codepoints beyond 0x10FFFF + surrogate = true; + slower = 0x90; + } + } else { + return false; + } + + // eat the rest of this multi-byte character + while (positions_left) { + positions_left--; + i++; + c = str[i]; + if (!c) + return false; // end of string but not end of character sequence + + // non-character : EF BF [BE-BF] or F[0-7] [89AB]F BF [BE-BF] + if (nonchar && (!positions_left && c < 0xBE || + positions_left == 1 && c != 0xBF || + positions_left == 2 && 0x0F != (0x0F & c) )) { + nonchar = false; + } + if (!IsInUTF8Sequence(c) || overlong && c <= olupper || + surrogate && slower <= c || nonchar && !positions_left ) { + return false; + } + overlong = surrogate = false; + } + } + return true; +} + +bool IsStringUTF8(const char* str) { + return IsStringUTF8T(str); +} + +bool IsStringWideUTF8(const wchar_t* str) { + return IsStringUTF8T(str); +} + +template<typename Iter> +static inline bool DoLowerCaseEqualsASCII(Iter a_begin, + Iter a_end, + const char* b) { + for (Iter it = a_begin; it != a_end; ++it, ++b) { + if (!*b || 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); +} + +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); +} +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); +} + +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 StrNCaseCmp(str.c_str(), search.c_str(), search.length()) == 0; +} + +DataUnits GetByteDisplayUnits(int64 bytes) { + // The byte thresholds at which we display amounts. A byte count is displayed + // in unit U when kUnitThresholds[U] <= bytes < kUnitThresholds[U+1]. + // This must match the DataUnits enum. + static const int64 kUnitThresholds[] = { + 0, // DATA_UNITS_BYTE, + 3*1024, // DATA_UNITS_KILOBYTE, + 2*1024*1024, // DATA_UNITS_MEGABYTE, + 1024*1024*1024 // DATA_UNITS_GIGABYTE, + }; + + if (bytes < 0) { + NOTREACHED() << "Negative bytes value"; + return DATA_UNITS_BYTE; + } + + int unit_index = arraysize(kUnitThresholds); + while (--unit_index > 0) { + if (bytes >= kUnitThresholds[unit_index]) + break; + } + + DCHECK(unit_index >= DATA_UNITS_BYTE && unit_index <= DATA_UNITS_GIGABYTE); + return DataUnits(unit_index); +} + +// TODO(mpcomplete): deal with locale +// Byte suffixes. This must match the DataUnits enum. +static const wchar_t* const kByteStrings[] = { + L"B", + L"kB", + L"MB", + L"GB" +}; + +static const wchar_t* const kSpeedStrings[] = { + L"B/s", + L"kB/s", + L"MB/s", + L"GB/s" +}; + +std::wstring FormatBytesInternal(int64 bytes, + DataUnits units, + bool show_units, + const wchar_t* const* suffix) { + if (bytes < 0) { + NOTREACHED() << "Negative bytes value"; + return std::wstring(); + } + + DCHECK(units >= DATA_UNITS_BYTE && units <= DATA_UNITS_GIGABYTE); + + // Put the quantity in the right units. + double unit_amount = static_cast<double>(bytes); + for (int i = 0; i < units; ++i) + unit_amount /= 1024.0; + + wchar_t tmp[64]; + // If the first decimal digit is 0, don't show it. + double int_part; + double fractional_part = modf(unit_amount, &int_part); + modf(fractional_part * 10, &int_part); + if (int_part == 0) + SWPrintF(tmp, arraysize(tmp), L"%lld", static_cast<int64>(unit_amount)); + else + SWPrintF(tmp, arraysize(tmp), L"%.1lf", unit_amount); + + std::wstring ret(tmp); + if (show_units) { + ret += L" "; + ret += suffix[units]; + } + + return ret; +} + +std::wstring FormatBytes(int64 bytes, DataUnits units, bool show_units) { + return FormatBytesInternal(bytes, units, show_units, kByteStrings); +} + +std::wstring FormatSpeed(int64 bytes, DataUnits units, bool show_units) { + return FormatBytesInternal(bytes, units, show_units, kSpeedStrings); +} + +template<class StringType> +void DoReplaceSubstringsAfterOffset(StringType* str, + typename StringType::size_type start_offset, + const StringType& find_this, + const StringType& replace_with) { + 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(); + } +} + +void ReplaceSubstringsAfterOffset(std::wstring* str, + std::wstring::size_type start_offset, + const std::wstring& find_this, + const std::wstring& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with); +} + +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); +} + +// 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, or if the buffer is not +// large enough to accommodate the formatted string without truncation, the +// number of characters that would be in the fully-formatted string. +inline int vsnprintfT(char* buffer, + size_t buf_size, + const char* format, + va_list argptr) { + return VSNPrintF(buffer, buf_size, format, argptr); +} + +inline int vsnprintfT(wchar_t* buffer, + size_t buf_size, + const wchar_t* format, + va_list argptr) { + return VSWPrintF(buffer, buf_size, format, argptr); +} + +// Templatized backend for StringPrintF/StringAppendF. This does not finalize +// the va_list, the caller is expected to do that. +template <class char_type> +static void StringAppendVT( + std::basic_string<char_type, std::char_traits<char_type> >* dst, + const char_type* format, + va_list ap) { + + // First try with a small fixed size buffer. + // This buffer size should be kept in sync with StringUtilTest.GrowBoundary. + const int kStackLength = 1024; + char_type stack_buf[kStackLength]; + + // It's possible for methods that use a va_list 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. It is not guaranteed that assignment is a copy, and + // va_copy is not supported by VC, so the UnitTest tests this capability. + va_list backup_ap = ap; + int result = vsnprintfT(stack_buf, kStackLength, format, backup_ap); + va_end(backup_ap); + + if (result >= 0 && result < kStackLength) { + // It fit. + dst->append(stack_buf, result); + return; + } + + int mem_length = result; + + // vsnprintfT may have failed for some reason other than an insufficient + // buffer, such as an invalid characer. Check that the requested buffer + // size is smaller than what was already attempted + if (mem_length < 0 || mem_length < kStackLength) { + DLOG(WARNING) << "Unable to compute size of the requested string."; + return; + } + + mem_length++; // Include the NULL terminator. + scoped_ptr<char_type> mem_buf(new char_type[mem_length]); + + // Do the printf. + result = vsnprintfT(mem_buf.get(), mem_length, format, ap); + DCHECK(result < mem_length); + if (result < 0) { + DLOG(WARNING) << "Unable to printf the requested string."; + return; + } + + dst->append(mem_buf.get(), result); +} + +std::string Uint64ToString(uint64 value) { + return StringPrintf("%llu", value); +} + +std::string Int64ToString(int64 value) { + return StringPrintf("%I64d", value); +} + +std::wstring Int64ToWString(int64 value) { + return StringPrintf(L"%I64d", value); +} + +std::string IntToString(int value) { + return StringPrintf("%d", value); +} + +std::wstring IntToWString(int value) { + return StringPrintf(L"%d", value); +} + +inline void StringAppendV(std::string* dst, const char* format, va_list ap) { + StringAppendVT<char>(dst, format, ap); +} + +inline void StringAppendV(std::wstring* dst, + const wchar_t* format, + va_list ap) { + StringAppendVT<wchar_t>(dst, format, ap); +} + +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; +} + +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; +} + +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; +} + +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; +} + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +void StringAppendF(std::wstring* dst, const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +template<typename STR> +static void SplitStringT(const STR& str, + const typename STR::value_type s, + bool trim_whitespace, + std::vector<STR>* r) { + size_t last = 0; + size_t i; + size_t c = str.size(); + for (i = 0; i <= c; ++i) { + if (i == c || str[i] == s) { + size_t len = i - last; + STR tmp = str.substr(last, len); + if (trim_whitespace) { + STR t_tmp; + TrimWhitespace(tmp, TRIM_ALL, &t_tmp); + r->push_back(t_tmp); + } else { + r->push_back(tmp); + } + last = i + 1; + } + } +} + +void SplitString(const std::wstring& str, + wchar_t s, + std::vector<std::wstring>* r) { + SplitStringT(str, s, true, r); +} + +void SplitString(const std::string& str, + char s, + std::vector<std::string>* r) { + SplitStringT(str, s, true, r); +} + +void SplitStringDontTrim(const std::wstring& str, + wchar_t s, + std::vector<std::wstring>* r) { + SplitStringT(str, s, false, r); +} + +void SplitStringDontTrim(const std::string& str, + char s, + std::vector<std::string>* r) { + SplitStringT(str, s, false, r); +} + +void SplitStringAlongWhitespace(const std::wstring& str, + std::vector<std::wstring>* result) { + 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)); + } +} + +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + size_t* offset) { + std::vector<size_t> offsets; + std::wstring result = ReplaceStringPlaceholders(format_string, a, + std::wstring(), + std::wstring(), + std::wstring(), &offsets); + DCHECK(offsets.size() == 1); + if (offset) { + *offset = offsets[0]; + } + return result; +} + +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + const std::wstring& b, + std::vector<size_t>* offsets) { + return ReplaceStringPlaceholders(format_string, a, b, std::wstring(), + std::wstring(), offsets); +} + +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + const std::wstring& b, + const std::wstring& c, + std::vector<size_t>* offsets) { + return ReplaceStringPlaceholders(format_string, a, b, c, std::wstring(), + offsets); +} + +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + const std::wstring& b, + const std::wstring& c, + const std::wstring& d, + std::vector<size_t>* offsets) { + // We currently only support up to 4 place holders ($1 through $4), although + // it's easy enough to add more. + const std::wstring* subst_texts[] = { &a, &b, &c, &d }; + + std::wstring formatted; + formatted.reserve(format_string.length() + a.length() + + b.length() + c.length() + d.length()); + + std::vector<ReplacementOffset> r_offsets; + + // Replace $$ with $ and $1-$4 with placeholder text if it exists. + for (std::wstring::const_iterator i = format_string.begin(); + i != format_string.end(); ++i) { + if ('$' == *i) { + if (i + 1 != format_string.end()) { + ++i; + DCHECK('$' == *i || ('1' <= *i && *i <= '4')) << + "Invalid placeholder: " << *i; + if ('$' == *i) { + formatted.push_back('$'); + } else { + int index = *i - '1'; + if (offsets) { + ReplacementOffset r_offset(index, + static_cast<int>(formatted.size())); + r_offsets.insert(std::lower_bound(r_offsets.begin(), + r_offsets.end(), r_offset, + &CompareParameter), + r_offset); + } + formatted.append(*subst_texts[index]); + } + } + } else { + formatted.push_back(*i); + } + } + if (offsets) { + for (std::vector<ReplacementOffset>::const_iterator i = r_offsets.begin(); + i != r_offsets.end(); ++i) { + offsets->push_back(i->offset); + } + } + return formatted; +} + +template <class CHAR> +static bool IsWildcard(CHAR character) { + return character == '*' || character == '?'; +} + +// Move the strings pointers to the point where they start to differ. +template <class CHAR> +static void EatSameChars(const CHAR** pattern, const CHAR** string) { + bool escaped = false; + while (**pattern && **string) { + if (!escaped && 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 (!escaped && **pattern == L'\\') { + escaped = true; + (*pattern)++; + continue; + } + + // Check if the chars match, if so, increment the ptrs. + if (**pattern == **string) { + (*pattern)++; + (*string)++; + } 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 (escaped) { + (*pattern)--; + } + return; + } + + escaped = false; + } +} + +template <class CHAR> +static void EatWildcard(const CHAR** pattern) { + while(**pattern) { + if (!IsWildcard(**pattern)) + return; + (*pattern)++; + } +} + +template <class CHAR> +static bool MatchPatternT(const CHAR* eval, const CHAR* pattern) { + // Eat all the matching chars. + EatSameChars(&pattern, &eval); + + // If the string is empty, then the pattern must be empty too, or contains + // only wildcards. + if (*eval == 0) { + EatWildcard(&pattern); + if (*pattern) + return false; + return true; + } + + // Pattern is empty but not string, this is not a match. + if (*pattern == 0) + 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. + if (pattern[0] == '?') { + if (MatchPatternT(eval, pattern + 1) || + MatchPatternT(eval + 1, pattern + 1)) + return true; + } + + // This is a *, try to match all the possible substrings with the remainder + // of the pattern. + if (pattern[0] == '*') { + while (*eval) { + if (MatchPatternT(eval, pattern + 1)) + return true; + eval++; + } + + // We reached the end of the string, let see if the pattern contains only + // wildcards. + if (*eval == 0) { + EatWildcard(&pattern); + if (*pattern) + return false; + return true; + } + } + + return false; +} + +bool MatchPattern(const std::wstring& eval, const std::wstring& pattern) { + return MatchPatternT(eval.c_str(), pattern.c_str()); +} + +bool MatchPattern(const std::string& eval, const std::string& pattern) { + return MatchPatternT(eval.c_str(), pattern.c_str()); +} diff --git a/base/string_util.h b/base/string_util.h new file mode 100644 index 0000000..e5fd147 --- /dev/null +++ b/base/string_util.h @@ -0,0 +1,504 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This file defines utility functions for working with strings. + +#ifndef BASE_STRING_UTIL_H__ +#define BASE_STRING_UTIL_H__ + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +// Safe standard library wrappers for all platforms. The Str* variants +// operate on NUL-terminated char* strings, like the standard library's str* +// functions. + +// Copy at most (dst_size - 1) characters from src to dest, guaranteeing dst +// will be NUL-terminated. If the string is copied without truncation, +// returns true. dst is undefined if the string cannot be copied without +// truncation, and the function will either return false or cause termination. +bool StrCpy(char* dest, const char* src, size_t dst_size); + +// As with StrCpy, but copies at most the minimum of (dst_size - 1) and +// src_size characters. +bool StrNCpy(char* dest, const char* src, size_t dst_size, size_t src_size); + +// Compare 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); + +// Wrapper for vsnprintf, snprintf that always NUL-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); +int SNPrintF(char* buffer, size_t size, const char* format, ...); + +// The Wcs* variants operate on NUL-terminated wchar_t* strings, like the +// standard library's wcs* functions. Otherwise, these behave the same as +// the Str* variants above. + +bool WcsCpy(wchar_t* dest, const wchar_t* src, size_t dst_size); +bool WcsNCpy(wchar_t* dest, const wchar_t* src, size_t dst_size); + +int VSWPrintF(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments); +int SWPrintF(wchar_t* buffer, size_t size, const wchar_t* format, ...); + +// Some of these implementations need to be inlined. + +#if defined(WIN32) +#include "base/string_util_win.h" +#elif defined(__APPLE__) +#include "base/string_util_mac.h" +#else +#error Define string operations appropriately for your platform +#endif + +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; +} + +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; +} + +// Returns a reference to a globally unique empty string that functions can +// return. Use this to avoid static construction of strings, not to replace +// any and all uses of "std::string()" as nicer-looking sugar. +// These functions are threadsafe. +const std::string& EmptyString(); +const std::wstring& EmptyWString(); + +extern const wchar_t kWhitespaceWide[]; +extern const char kWhitespaceASCII[]; + +// Names of codepages (charsets) understood by icu. +extern const char* const kCodepageUTF8; + +// Removes characters in trim_chars from the beginning and end of input. +// NOTE: Safe to use the same variable for both input and output. +bool TrimString(const std::wstring& input, + wchar_t trim_chars[], + std::wstring* output); +bool TrimString(const std::string& input, + char trim_chars[], + std::string* output); + +// Trims any whitespace from either end of the input string. Returns where +// whitespace was found. The non-wide version of this function only looks for +// ASCII whitespace; UTF-8 code-points are not searched for (use the wide +// version instead). +// 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, +}; +TrimPositions TrimWhitespace(const std::wstring& input, + TrimPositions positions, + std::wstring* output); +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. +std::wstring CollapseWhitespace(const std::wstring& text, + bool trim_sequences_with_line_breaks); + +// These convert between ASCII (7-bit) and UTF16 strings. +std::string WideToASCII(const std::wstring& wide); +std::wstring ASCIIToWide(const std::string& ascii); + +// These convert between UTF8 and UTF16 strings. They are potentially slow, +// so avoid unnecessary conversions. Most things should be in UTF16. +std::string WideToUTF8(const std::wstring& wide); +std::wstring UTF8ToWide(const std::string& utf8); + +// Converts between wide strings and whatever the native multibyte encoding +// is. The native multibyte encoding on English machines will often Latin-1, +// but could be ShiftJIS or even UTF-8, among others. +// +// These functions can be dangerous. Do not use unless you are sure you are +// giving them to/getting them from somebody who expects the current platform +// 8-bit encoding. +std::string WideToNativeMB(const std::wstring& wide); +std::wstring NativeMBToWide(const std::string& native_mb); + +// Defines the error handling modes of WideToCodepage and CodepageToWide. +class OnStringUtilConversionError { + 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, + }; + + private: + OnStringUtilConversionError(); +}; + +// 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. +bool WideToCodepage(const std::wstring& wide, + const char* codepage_name, + OnStringUtilConversionError::Type on_error, + std::string* encoded); +bool CodepageToWide(const std::string& encoded, + const char* codepage_name, + OnStringUtilConversionError::Type on_error, + std::wstring* wide); + +// Converts the given wide string to the corresponding Latin1. This will fail +// (return false) if any characters are more than 255. +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). +bool IsString8Bit(const std::wstring& str); +bool IsStringUTF8(const char* str); +bool IsStringWideUTF8(const wchar_t* str); +bool IsStringASCII(const std::wstring& str); +bool IsStringASCII(const std::string& str); + +// ASCII-specific tolower. The standard library's tolower is locale sensitive, +// so we don't want to use it here. +template <class Char> inline Char ToLowerASCII(Char c) { + return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c; +} + +// Converts the elements of the given string. This version uses a pointer to +// clearly differentiate it from the non-pointer variant. +template <class str> inline void StringToLowerASCII(str* s) { + for (typename str::iterator i = s->begin(); i != s->end(); ++i) + *i = ToLowerASCII(*i); +} + +template <class str> inline str StringToLowerASCII(const str& s) { + // for std::string and std::wstring + str output(s); + StringToLowerASCII(&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. +bool LowerCaseEqualsASCII(const std::string& a, const char* b); +bool LowerCaseEqualsASCII(const std::wstring& a, const char* b); + +// Same thing, but with string iterators instead. +bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, + std::string::const_iterator a_end, + const char* b); +bool LowerCaseEqualsASCII(std::wstring::const_iterator a_begin, + std::wstring::const_iterator a_end, + const char* b); +bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b); +bool LowerCaseEqualsASCII(const wchar_t* a_begin, + const wchar_t* a_end, + const char* b); + +// Returns true if str starts with search, or false otherwise. +// This only works on ASCII strings. +bool StartsWithASCII(const std::string& str, + const std::string& search, + bool case_sensitive); + +// Determines the type of ASCII character, independent of locale (the C +// library versions will change based on locale). +template <typename Char> +inline bool IsAsciiWhitespace(Char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} +template <typename Char> +inline bool IsAsciiAlpha(Char c) { + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); +} +template <typename Char> +inline bool IsAsciiDigit(Char c) { + return c >= '0' && c <= '9'; +} + +// Returns true if it's a whitespace character. +inline bool IsWhitespace(wchar_t c) { + return wcschr(kWhitespaceWide, c) != NULL; +} + +// TODO(mpcomplete): Decide if we should change these names to KIBI, etc, +// or if we should actually use metric units, or leave as is. +enum DataUnits { + DATA_UNITS_BYTE = 0, + DATA_UNITS_KILOBYTE, + DATA_UNITS_MEGABYTE, + DATA_UNITS_GIGABYTE, +}; + +// Return the unit type that is appropriate for displaying the amount of bytes +// passed in. +DataUnits GetByteDisplayUnits(int64 bytes); + +// Return a byte string in human-readable format, displayed in units appropriate +// specified by 'units', with an optional unit suffix. +// Ex: FormatBytes(512, DATA_UNITS_KILOBYTE, true) => "0.5 KB" +// Ex: FormatBytes(10*1024, DATA_UNITS_MEGABYTE, false) => "0.1" +std::wstring FormatBytes(int64 bytes, DataUnits units, bool show_units); + +// As above, but with "/s" units. +// Ex: FormatSpeed(512, DATA_UNITS_KILOBYTE, true) => "0.5 KB/s" +// Ex: FormatSpeed(10*1024, DATA_UNITS_MEGABYTE, false) => "0.1" +std::wstring FormatSpeed(int64 bytes, DataUnits units, bool show_units); + +// Return a number formated with separators in the user's locale way. +// Ex: FormatNumber(1234567) => 1,234,567 +std::wstring FormatNumber(int64 number); + +// Starting at |start_offset| (usually 0), look through |str| and replace all +// instances of |find_this| with |replace_with|. +// +// This does entire substrings; use std::replace in <algorithm> for single +// characters, for example: +// std::replace(str.begin(), str.end(), 'a', 'b'); +void ReplaceSubstringsAfterOffset(std::wstring* str, + std::wstring::size_type start_offset, + const std::wstring& find_this, + const std::wstring& replace_with); +void ReplaceSubstringsAfterOffset(std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with); + +// Specialized string-conversion functions. +std::string Uint64ToString(uint64 value); +std::string IntToString(int value); +std::string Int64ToString(int64 value); +std::wstring Int64ToWString(int64 value); +std::wstring IntToWString(int value); +int64 StringToInt64(const std::string& value); +int64 StringToInt64(const std::wstring& value); + +// Return a C++ string given printf-like input. +std::string StringPrintf(const char* format, ...); +std::wstring StringPrintf(const wchar_t* format, ...); + +// Store result into a supplied string and return it +const std::string& SStringPrintf(std::string* dst, const char* format, ...); +const std::wstring& SStringPrintf(std::wstring* dst, + const wchar_t* format, ...); + +// Append result to a supplied string +void StringAppendF(std::string* dst, const char* format, ...); +void StringAppendF(std::wstring* dst, const wchar_t* format, ...); + +// Lower-level routine that takes a va_list and appends to a specified +// string. All other routines are just convenience wrappers around it. +void StringAppendV(std::string* dst, const char* format, va_list ap); +void StringAppendV(std::wstring* dst, const wchar_t* format, va_list ap); + +// This is mpcomplete's pattern for saving a string copy when dealing with +// a function that writes results into a wchar_t[] and wanting the result to +// end up in a std::wstring. It ensures that the std::wstring's internal +// buffer has enough room to store the characters to be written into it, and +// sets its .length() attribute to the right value. +// +// The reserve() call allocates the memory required to hold the string +// plus a terminating null. This is done because resize() isn't +// guaranteed to reserve space for the null. The resize() call is +// simply the only way to change the string's 'length' member. +// +// XXX-performance: the call to wide.resize() takes linear time, since it fills +// the string's buffer with nulls. I call it to change the length of the +// string (needed because writing directly to the buffer doesn't do this). +// Perhaps there's a constant-time way to change the string's length. +template <class char_type> +inline char_type* WriteInto( + std::basic_string<char_type, std::char_traits<char_type>, + std::allocator<char_type> >* str, + size_t length_including_null) { + str->reserve(length_including_null); + str->resize(length_including_null - 1); + return &((*str)[0]); +} + +//----------------------------------------------------------------------------- +// CharTraits is provides wrappers with common function names for char/wchar_t +// specific CRT functions. + +template <class CharT> struct CharTraits { +}; + +template <> +struct CharTraits<char> { + static inline size_t length(const char* s) { + return strlen(s); + } + static inline bool copy(char* dst, size_t dst_size, const char* s) { + return StrCpy(dst, s, dst_size); + } + static inline bool copy_num(char* dst, size_t dst_size, const char* s, + size_t s_len) { + if (dst_size < (s_len + 1)) + return false; + memcpy(dst, s, s_len); + dst[s_len] = '\0'; + return true; + } +}; + +template <> +struct CharTraits<wchar_t> { + static inline size_t length(const wchar_t* s) { + return wcslen(s); + } + static inline bool copy(wchar_t* dst, size_t dst_size, const wchar_t* s) { + return WcsCpy(dst, s, dst_size); + } + static inline bool copy_num(wchar_t* dst, size_t dst_size, const wchar_t* s, + size_t s_len) { + if (dst_size < (s_len + 1)) + return false; + memcpy(dst, s, s_len * sizeof(wchar_t)); + dst[s_len] = '\0'; + return true; + } +}; + +//----------------------------------------------------------------------------- + +// Function objects to aid in comparing/searching strings. + +template<typename Char> struct CaseInsensitiveCompare { + public: + bool operator()(Char x, Char y) const { + return tolower(x) == tolower(y); + } +}; + +template<typename Char> struct CaseInsensitiveCompareASCII { + public: + bool operator()(Char x, Char y) const { + return ToLowerASCII(x) == ToLowerASCII(y); + } +}; + +//----------------------------------------------------------------------------- + +// Splits |str| into a vector of strings delimited by |s|. Append the results +// into |r| as they appear. 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. +void SplitString(const std::wstring& str, + wchar_t s, + std::vector<std::wstring>* r); +void SplitString(const std::string& str, + char s, + std::vector<std::string>* r); + +// The same as SplitString, but don't trim white space. +void SplitStringDontTrim(const std::wstring& str, + wchar_t s, + std::vector<std::wstring>* r); +void SplitStringDontTrim(const std::string& str, + char s, + std::vector<std::string>* r); + +// WARNING: this uses whitespace as defined by the HTML5 spec. If you need +// a function similar to this but want to trim all types of whitespace, then +// factor this out into a function that takes a string containing the characters +// that are treated as whitespace. +// +// Splits the string along whitespace (where whitespace is the five space +// characters defined by HTML 5). Each contiguous block of non-whitespace +// characters is added to result. +void SplitStringAlongWhitespace(const std::wstring& str, + std::vector<std::wstring>* result); + +// Replace $1-$2-$3 in the format string with |a| and |b| respectively. +// Additionally, $$ is replaced by $. The offset/offsets parameter here can be +// NULL. +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + size_t* offset); + +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + const std::wstring& b, + std::vector<size_t>* offsets); + +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + const std::wstring& b, + const std::wstring& c, + std::vector<size_t>* offsets); + +std::wstring ReplaceStringPlaceholders(const std::wstring& format_string, + const std::wstring& a, + const std::wstring& b, + const std::wstring& c, + const std::wstring& d, + std::vector<size_t>* offsets); + +// Returns true if the string passed in matches the pattern. The pattern +// string can contain wildcards like * and ? +// TODO(iyengar) This function may not work correctly for CJK strings as +// it does individual character matches. +// The backslash character (\) is an escape character for * and ? +bool MatchPattern(const std::wstring& string, const std::wstring& pattern); +bool MatchPattern(const std::string& string, const std::string& pattern); + +#endif // BASE_STRING_UTIL_H__ diff --git a/base/string_util_icu.cc b/base/string_util_icu.cc new file mode 100644 index 0000000..797ccbd --- /dev/null +++ b/base/string_util_icu.cc @@ -0,0 +1,201 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "base/string_util.h" + +#include <string.h> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/singleton.h" +#include "unicode/ucnv.h" +#include "unicode/numfmt.h" +#include "unicode/ustring.h" + +// Codepage <-> Wide ----------------------------------------------------------- + +// Convert a unicode string into the specified codepage_name. If the codepage +// isn't found, return false. +bool WideToCodepage(const std::wstring& wide, + const char* codepage_name, + OnStringUtilConversionError::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; + + const UChar* uchar_src; + int uchar_len; +#ifdef U_WCHAR_IS_UTF16 + uchar_src = wide.c_str(); + uchar_len = static_cast<int>(wide.length()); +#else // U_WCHAR_IS_UTF16 + // 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 points as there are UCS-4 characters, in case each + // character translates to a UTF-16 surrogate pair, and leave room for a NUL + // terminator. + std::vector<UChar> wide_uchar(wide.length() * 2 + 1); + u_strFromWCS(&wide_uchar[0], wide_uchar.size(), &uchar_len, + wide.c_str(), wide.length(), &status); + uchar_src = &wide_uchar[0]; + DCHECK(U_SUCCESS(status)) << "failed to convert wstring to UChar*"; +#endif // U_WCHAR_IS_UTF16 + + int encoded_max_length = UCNV_GET_MAX_BYTES_FOR_STRING(uchar_len, + ucnv_getMaxCharSize(converter)); + encoded->resize(encoded_max_length); + + // Setup our error handler. + switch (on_error) { + case OnStringUtilConversionError::FAIL: + ucnv_setFromUCallBack(converter, UCNV_FROM_U_CALLBACK_STOP, 0, + NULL, NULL, &status); + break; + case OnStringUtilConversionError::SKIP: + 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; +} + +// Converts a string of the given codepage into unicode. +// If the codepage isn't found, return false. +bool CodepageToWide(const std::string& encoded, + const char* codepage_name, + OnStringUtilConversionError::Type on_error, + std::wstring* wide) { + wide->clear(); + + UErrorCode status = U_ZERO_ERROR; + UConverter* converter = ucnv_open(codepage_name, &status); + if (!U_SUCCESS(status)) + return false; + + // The worst case is all the input characters are non-BMP (32-bit) ones. + size_t uchar_max_length = encoded.length() * 2 + 1; + + UChar* uchar_dst; +#ifdef U_WCHAR_IS_UTF16 + uchar_dst = WriteInto(wide, uchar_max_length); +#else + // When wchar_t is wider than UChar (16 bits), convert into a temporary + // UChar* buffer. + std::vector<UChar> wide_uchar(uchar_max_length); + uchar_dst = &wide_uchar[0]; +#endif // U_WCHAR_IS_UTF16 + + // Setup our error handler. + switch (on_error) { + case OnStringUtilConversionError::FAIL: + ucnv_setToUCallBack(converter, UCNV_TO_U_CALLBACK_STOP, 0, + NULL, NULL, &status); + break; + case OnStringUtilConversionError::SKIP: + ucnv_setToUCallBack(converter, UCNV_TO_U_CALLBACK_SKIP, 0, + NULL, NULL, &status); + break; + default: + NOTREACHED(); + } + + int actual_size = ucnv_toUChars(converter, + uchar_dst, + static_cast<int>(uchar_max_length), + encoded.data(), + static_cast<int>(encoded.length()), + &status); + ucnv_close(converter); + if (!U_SUCCESS(status)) { + wide->clear(); // Make sure the output is empty on error. + return false; + } + +#ifndef U_WCHAR_IS_UTF16 + // When wchar_t is wider than UChar (16 bits), it's not possible to wind up + // with any more wchar_t elements than UChar elements. ucnv_toUChars + // returns the number of UChar elements not including the NUL terminator, so + // leave extra room for that. + u_strToWCS(WriteInto(wide, actual_size + 1), actual_size + 1, &actual_size, + uchar_dst, actual_size, &status); + DCHECK(U_SUCCESS(status)) << "failed to convert UChar* to wstring"; +#endif // U_WCHAR_IS_UTF16 + + wide->resize(actual_size); + return true; +} + +// Number formatting ----------------------------------------------------------- + +// TODO: http://b/id=1092584 Come up with a portable pthread_once, and use +// that to keep a singleton instead of putting it in the platform-dependent +// file. +NumberFormat* NumberFormatSingleton(); + +std::wstring FormatNumber(int64 number) { + NumberFormat* number_format = NumberFormatSingleton(); + if (!number_format) { + // As a fallback, just return the raw number in a string. + return StringPrintf(L"%lld", number); + } + UnicodeString ustr; + number_format->format(number, ustr); + +#ifdef U_WCHAR_IS_UTF16 + return std::wstring(ustr.getBuffer(), + static_cast<std::wstring::size_type>(ustr.length())); +#else // U_WCHAR_IS_UTF16 + wchar_t buffer[64]; // A int64 is less than 20 chars long, so 64 chars + // leaves plenty of room for formating stuff. + int length = 0; + UErrorCode error = U_ZERO_ERROR; + u_strToWCS(buffer, 64, &length, ustr.getBuffer(), ustr.length() , &error); + if (U_FAILURE(error)) { + NOTREACHED(); + // As a fallback, just return the raw number in a string. + return StringPrintf(L"%lld", number); + } + return std::wstring(buffer, static_cast<std::wstring::size_type>(length)); +#endif // U_WCHAR_IS_UTF16 +} diff --git a/base/string_util_mac.cc b/base/string_util_mac.cc new file mode 100644 index 0000000..4c5f3dc --- /dev/null +++ b/base/string_util_mac.cc @@ -0,0 +1,239 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/string_util.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <pthread.h> +#include <string> +#include <vector> +#include "base/logging.h" +#include "base/scoped_cftyperef.h" +#include "unicode/numfmt.h" + +// Can't use strlcpy/wcslcpy, because they always returns the length of src, +// making it impossible to detect overflow. Because the reimplementation is +// too large to inline, StrNCpy and WcsNCpy are in this file, but since they +// don't make any non-inlined calls, there's no penalty relative to the libc +// routines. +template<typename CharType> +static inline bool StrNCpyT(CharType* dst, const CharType* src, + size_t dst_size, size_t src_size) { + // The initial value of count has room for a NUL terminator. + size_t count = std::min(dst_size, src_size + 1); + if (count == 0) + return false; + + // Copy up to (count - 1) bytes, or until reaching a NUL terminator + while (--count != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + + // If the break never occurred, append a NUL terminator + if (count == 0) { + *dst = '\0'; + + // If the string was truncated, return false + if (*src != '\0') + return false; + } + + return true; +} + +bool StrNCpy(char* dst, const char* src, + size_t dst_size, size_t src_size) { + return StrNCpyT(dst, src, dst_size, src_size); +} + +bool WcsNCpy(wchar_t* dst, const wchar_t* src, + size_t dst_size, size_t src_size) { + return StrNCpyT(dst, src, dst_size, src_size); +} + +static NumberFormat* number_format_singleton = NULL; +static CFDateFormatterRef date_formatter = NULL; +static CFDateFormatterRef time_formatter = NULL; + +static void DoInitializeStatics() { + UErrorCode status = U_ZERO_ERROR; + number_format_singleton = NumberFormat::createInstance(status); + DCHECK(U_SUCCESS(status)); + + scoped_cftyperef<CFLocaleRef> user_locale(CFLocaleCopyCurrent()); + date_formatter = CFDateFormatterCreate(NULL, + user_locale, + kCFDateFormatterShortStyle, // date + NULL); // time + DCHECK(date_formatter); + time_formatter = CFDateFormatterCreate(NULL, + user_locale, + NULL, // date + kCFDateFormatterShortStyle); // time + DCHECK(time_formatter); +} + +static void InitializeStatics() { + static pthread_once_t pthread_once_initialized = PTHREAD_ONCE_INIT; + pthread_once(&pthread_once_initialized, DoInitializeStatics); +} + +// Convert the supplied cfsring into the specified encoding, and return it as +// an STL string of the template type. Returns an empty string on failure. +template<typename StringType> +static StringType CFStringToSTLStringWithEncodingT(CFStringRef cfstring, + CFStringEncoding encoding) { + CFIndex length = CFStringGetLength(cfstring); + if (length == 0) + return StringType(); + + CFRange whole_string = CFRangeMake(0, length); + CFIndex out_size; + CFIndex converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + NULL, // buffer + 0, // maxBufLen + &out_size); + DCHECK(converted != 0 && out_size != 0); + 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; + + // Make sure that integer truncation didn't occur. For the conversions done + // here, it never should. + DCHECK(((out_size * sizeof(UInt8)) % + sizeof(typename StringType::value_type)) == 0); + + std::vector<typename StringType::value_type> out_buffer(elements); + converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + reinterpret_cast<UInt8*>(&out_buffer[0]), + out_size, + NULL); // usedBufLen + DCHECK(converted != 0); + if (converted == 0) + return StringType(); + + out_buffer[elements - 1] = '\0'; + return StringType(&out_buffer[0]); +} + +// 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. +template<typename OutStringType, typename InStringType> +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(); + + scoped_cftyperef<CFStringRef> cfstring( + CFStringCreateWithBytesNoCopy(NULL, + reinterpret_cast<const UInt8*>(in.c_str()), + in_length * + sizeof(typename InStringType::value_type), + in_encoding, + false, + kCFAllocatorNull)); + DCHECK(cfstring); + if (!cfstring) + return OutStringType(); + + return CFStringToSTLStringWithEncodingT<OutStringType>(cfstring, + out_encoding); +} + +// 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__ +#if defined(__WCHAR_MAX__) && __WCHAR_MAX__ == 0xffff +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF16BE; +#else // __WCHAR_MAX__ +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32BE; +#endif // __WCHAR_MAX__ +#else // __BIG_ENDIAN__ +#if defined(__WCHAR_MAX__) && __WCHAR_MAX__ == 0xffff +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF16LE; +#else // __WCHAR_MAX__ +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32LE; +#endif // __WCHAR_MAX__ +#endif // __BIG_ENDIAN__ + +std::string WideToUTF8(const std::wstring& wide) { + return STLStringToSTLStringWithEncodingsT<std::string>( + wide, kWideStringEncoding, kNarrowStringEncoding); +} + +std::wstring UTF8ToWide(const std::string& utf8) { + return STLStringToSTLStringWithEncodingsT<std::wstring>( + utf8, kNarrowStringEncoding, kWideStringEncoding); +} + +// Technically, the native multibyte encoding would be the encoding returned +// by CFStringGetSystemEncoding or GetApplicationTextEncoding, but I can't +// imagine anyone needing or using that from these APIs, so just treat UTF-8 +// as though it were the native multibyte encoding. +std::string WideToNativeMB(const std::wstring& wide) { + return WideToUTF8(wide); +} + +std::wstring NativeMBToWide(const std::string& native_mb) { + return UTF8ToWide(native_mb); +} + +NumberFormat* NumberFormatSingleton() { + InitializeStatics(); + return number_format_singleton; +} + +int64 StringToInt64(const std::string& value) { + return atoll(value.c_str()); +} + +int64 StringToInt64(const std::wstring& value) { + return wcstoll(value.c_str(), NULL, 10); +} diff --git a/base/string_util_mac.h b/base/string_util_mac.h new file mode 100644 index 0000000..26201b7 --- /dev/null +++ b/base/string_util_mac.h @@ -0,0 +1,62 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_STRING_UTIL_MAC_H__ +#define BASE_STRING_UTIL_MAC_H__ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <wchar.h> + +inline bool StrCpy(char* dst, const char* src, size_t dst_size) { + return strlcpy(dst, src, dst_size) < dst_size; +} + +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 bool WcsCpy(char* dst, const char* src, size_t dst_size) { + return strlcpy(dst, src, dst_size) < dst_size; +} + +inline int VSWPrintF(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + return vswprintf(buffer, size, format, arguments); +} + +// StrNCpy and WcsNCpy are not inline, so they're implemented in the .cc file. + +#endif // BASE_STRING_UTIL_MAC_H__ diff --git a/base/string_util_unittest.cc b/base/string_util_unittest.cc new file mode 100644 index 0000000..c6ff622 --- /dev/null +++ b/base/string_util_unittest.cc @@ -0,0 +1,848 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <sstream> +#include <stdarg.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +} + +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}, + {"\x85Test String\xa0\x20", TRIM_ALL, "Test String", TRIM_ALL}, +}; + +TEST(StringUtilTest, TrimWhitespace) { + std::wstring output; // Allow contents to carry over to next testcase + for (int i = 0; i < arraysize(trim_cases); ++i) { + const trim_case& value = trim_cases[i]; + EXPECT_EQ(value.return_value, + TrimWhitespace(value.input, value.positions, &output)); + EXPECT_EQ(value.output, output); + } + + // Test that TrimWhitespace() can take the same string for input and output + output = L" This is a test \r\n"; + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(L"This is a test", output); + + // Once more, but with a string of whitespace + output = L" \r\n"; + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(L"", output); + + std::string output_ascii; + for (int 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 (int 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 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", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", + + // Test a character that takes more than 16-bits. This will depend on whether + // wchar_t is 16 or 32 bits. + #ifdef WIN32 + L"\xd800\xdf00", + #else + "\x10300, + #endif +}; + +TEST(StringUtilTest, 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 (int 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(StringUtilTest, 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(StringUtilTest, 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); +} + +TEST(StringUtilTest, ConvertCodepageUTF8) { + // Make sure WideToCodepage works like WideToUTF8. + for (int i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::string expected(WideToUTF8(kConvertRoundtripCases[i])); + std::string utf8; + EXPECT_TRUE(WideToCodepage(kConvertRoundtripCases[i], kCodepageUTF8, + OnStringUtilConversionError::SKIP, &utf8)); + EXPECT_EQ(expected, utf8); + } +} + +TEST(StringUtilTest, ConvertBetweenCodepageAndWide) { + static const struct { + const char* codepage_name; + const char* encoded; + OnStringUtilConversionError::Type on_error; + bool success; + const wchar_t* wide; + } kConvertCodepageCases[] = { + // Test a case where the input can no be decoded, using both SKIP and FAIL + // error handling rules. "A7 41" is valid, but "A6" isn't. + {"big5", + "\xA7\x41\xA6", + OnStringUtilConversionError::FAIL, + false, + L""}, + {"big5", + "\xA7\x41\xA6", + OnStringUtilConversionError::SKIP, + true, + L"\x4F60"}, + // 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", + OnStringUtilConversionError::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"}, + // Chinese Simplified (GB2312) + {"gb2312", + "\xC4\xE3\xBA\xC3", + OnStringUtilConversionError::FAIL, + true, + L"\x4F60\x597D"}, + // Chinese Traditional (BIG5) + {"big5", + "\xA7\x41\xA6\x6E", + OnStringUtilConversionError::FAIL, + true, + L"\x4F60\x597D"}, + // Greek (ISO-8859) + {"iso-8859-7", + "\xE3\xE5\xE9\xDC" " " "\xF3\xEF\xF5", + OnStringUtilConversionError::FAIL, + true, + L"\x03B3\x03B5\x03B9\x03AC" L" " L"\x03C3\x03BF\x03C5"}, + // Hebrew (Windows) + {"windows-1255", /* to be replaced with "iso-8859-8-I"? */ + "\xF9\xD1\xC8\xEC\xE5\xC9\xED", + OnStringUtilConversionError::FAIL, + true, + L"\x05E9\x05C1\x05B8\x05DC\x05D5\x05B9\x05DD"}, + // Hindi Devanagari (ISCII) + {"iscii-dev", + "\xEF\x42" "\xC6\xCC\xD7\xE8\xB3\xDA\xCF", + OnStringUtilConversionError::FAIL, + true, + L"\x0928\x092E\x0938\x094D\x0915\x093E\x0930"}, + // Korean (EUC) + {"euc-kr", + "\xBE\xC8\xB3\xE7\xC7\xCF\xBC\xBC\xBF\xE4", + OnStringUtilConversionError::FAIL, + true, + L"\xC548\xB155\xD558\xC138\xC694"}, + // Japanese (EUC) + {"euc-jp", + "\xA4\xB3\xA4\xF3\xA4\xCB\xA4\xC1\xA4\xCF", + OnStringUtilConversionError::FAIL, + true, + L"\x3053\x3093\x306B\x3061\x306F"}, + // Japanese (ISO-2022) + {"iso-2022-jp", + "\x1B\x24\x42" "\x24\x33\x24\x73\x24\x4B\x24\x41\x24\x4F" "\x1B\x28\x42", + OnStringUtilConversionError::FAIL, + true, + L"\x3053\x3093\x306B\x3061\x306F"}, + // Japanese (Shift-JIS) + {"sjis", + "\x82\xB1\x82\xF1\x82\xC9\x82\xBF\x82\xCD", + OnStringUtilConversionError::FAIL, + true, + L"\x3053\x3093\x306B\x3061\x306F"}, + // Russian (KOI8) + {"koi8-r", + "\xDA\xC4\xD2\xC1\xD7\xD3\xD4\xD7\xD5\xCA\xD4\xC5", + OnStringUtilConversionError::FAIL, + true, + L"\x0437\x0434\x0440\x0430\x0432\x0441\x0442\x0432" + L"\x0443\x0439\x0442\x0435"}, + // Thai (ISO-8859) + {"windows-874", /* to be replaced with "iso-8859-11". */ + "\xCA\xC7\xD1\xCA\xB4\xD5" "\xA4\xC3\xD1\xBA", + OnStringUtilConversionError::FAIL, + true, + L"\x0E2A\x0E27\x0E31\x0E2A\x0E14\x0E35" + L"\x0E04\x0E23\x0e31\x0E1A"}, + }; + + for (int i = 0; i < arraysize(kConvertCodepageCases); ++i) { + 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. + if (success && + kConvertCodepageCases[i].on_error == + OnStringUtilConversionError::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", + OnStringUtilConversionError::FAIL, &encoded)); + EXPECT_TRUE(encoded.empty()); + EXPECT_TRUE(WideToCodepage(L"Chinese\xff27", "iso-8859-1", + OnStringUtilConversionError::SKIP, &encoded)); + EXPECT_STREQ("Chinese", encoded.c_str()); + +#ifdef WIN32 + // 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", + OnStringUtilConversionError::FAIL, &encoded)); + EXPECT_TRUE(encoded.empty()); + EXPECT_TRUE(WideToCodepage(L"a\xd800z", "iso-8859-1", + OnStringUtilConversionError::SKIP, &encoded)); + EXPECT_STREQ("az", encoded.c_str()); +#endif + + // Invalid characters should fail. + EXPECT_TRUE(WideToCodepage(L"a\xffffz", "iso-8859-1", + OnStringUtilConversionError::SKIP, &encoded)); + EXPECT_STREQ("az", encoded.c_str()); + + // Invalid codepages should fail. + EXPECT_FALSE(WideToCodepage(L"Hello, world", "awesome-8571-2", + OnStringUtilConversionError::SKIP, &encoded)); +} + +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 (int 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)); +} + +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"}, +}; + +TEST(StringUtilTest, LowerCaseEqualsASCII) { + for (int i = 0; i < arraysize(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, GetByteDisplayUnits) { + static const struct { + int64 bytes; + DataUnits expected; + } cases[] = { + {0, DATA_UNITS_BYTE}, + {512, DATA_UNITS_BYTE}, + {10*1024, DATA_UNITS_KILOBYTE}, + {10*1024*1024, DATA_UNITS_MEGABYTE}, + {10LL*1024*1024*1024, DATA_UNITS_GIGABYTE}, + {~(1LL<<63), DATA_UNITS_GIGABYTE}, +#ifdef NDEBUG + {-1, DATA_UNITS_BYTE}, +#endif + }; + + for (int i = 0; i < arraysize(cases); ++i) + EXPECT_EQ(cases[i].expected, GetByteDisplayUnits(cases[i].bytes)); +} + +TEST(StringUtilTest, FormatBytes) { + static const struct { + int64 bytes; + DataUnits units; + const wchar_t* expected; + const wchar_t* expected_with_units; + } cases[] = { + {0, DATA_UNITS_BYTE, L"0", L"0 B"}, + {512, DATA_UNITS_BYTE, L"512", L"512 B"}, + {512, DATA_UNITS_KILOBYTE, L"0.5", L"0.5 kB"}, + {1024*1024, DATA_UNITS_KILOBYTE, L"1024", L"1024 kB"}, + {1024*1024, DATA_UNITS_MEGABYTE, L"1", L"1 MB"}, + {1024*1024*1024, DATA_UNITS_GIGABYTE, L"1", L"1 GB"}, + {10LL*1024*1024*1024, DATA_UNITS_GIGABYTE, L"10", L"10 GB"}, + {~(1LL<<63), DATA_UNITS_GIGABYTE, L"8589934592", L"8589934592 GB"}, + // Make sure the first digit of the fractional part works. + {1024*1024 + 103, DATA_UNITS_KILOBYTE, L"1024.1", L"1024.1 kB"}, + {1024*1024 + 205 * 1024, DATA_UNITS_MEGABYTE, L"1.2", L"1.2 MB"}, + {1024*1024*1024 + (927 * 1024*1024), DATA_UNITS_GIGABYTE, + L"1.9", L"1.9 GB"}, + {10LL*1024*1024*1024, DATA_UNITS_GIGABYTE, L"10", L"10 GB"}, +#ifdef NDEBUG + {-1, DATA_UNITS_BYTE, L"", L""}, +#endif + }; + + for (int i = 0; i < arraysize(cases); ++i) { + EXPECT_EQ(cases[i].expected, + FormatBytes(cases[i].bytes, cases[i].units, false)); + EXPECT_EQ(cases[i].expected_with_units, + FormatBytes(cases[i].bytes, cases[i].units, true)); + } +} + +TEST(StringUtilTest, ReplaceSubstringsAfterOffset) { + static const struct { + wchar_t* str; + std::wstring::size_type start_offset; + wchar_t* find_this; + wchar_t* replace_with; + wchar_t* expected; + } cases[] = { + {L"aaa", 0, L"a", L"b", L"bbb"}, + {L"abb", 0, L"ab", L"a", L"ab"}, + {L"Removing some substrings inging", 0, L"ing", L"", L"Remov some substrs "}, + {L"Not found", 0, L"x", L"0", L"Not found"}, + {L"Not found again", 5, L"x", L"0", L"Not found again"}, + {L" Making it much longer ", 0, L" ", L"Four score and seven years ago", + L"Four score and seven years agoMakingFour score and seven years agoit" + L"Four score and seven years agomuchFour score and seven years agolonger" + L"Four score and seven years ago"}, + {L"Invalid offset", 9999, L"t", L"foobar", L"Invalid offset"}, + {L"Replace me only me once", 9, L"me ", L"", L"Replace me only once"}, + {L"abababab", 2, L"ab", L"c", L"abccc"}, + }; + + for (int i = 0; i < arraysize(cases); i++) { + std::wstring str(cases[i].str); + ReplaceSubstringsAfterOffset(&str, cases[i].start_offset, + cases[i].find_this, cases[i].replace_with); + EXPECT_EQ(cases[i].expected, str); + } +} + +TEST(StringUtilTest, IntToString) { + static const struct { + int input; + std::string output; + } cases[] = { + {0, "0"}, + {42, "42"}, + {-42, "-42"}, + {INT_MAX, "2147483647"}, + {INT_MIN, "-2147483648"}, + }; + + for (int i = 0; i < arraysize(cases); ++i) + EXPECT_EQ(cases[i].output, IntToString(cases[i].input)); +} + +TEST(StringUtilTest, Uint64ToString) { + static const struct { + uint64 input; + std::string output; + } cases[] = { + {0, "0"}, + {42, "42"}, + {INT_MAX, "2147483647"}, + {kuint64max, "18446744073709551615"}, + }; + + for (int i = 0; i < arraysize(cases); ++i) + EXPECT_EQ(cases[i].output, Uint64ToString(cases[i].input)); +} + +// 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 = 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(StringUtilTest, StringPrintfEmptyFormat) { + const char* empty = ""; + EXPECT_EQ("", StringPrintf(empty)); + EXPECT_EQ("", StringPrintf("%s", "")); +} + +TEST(StringUtilTest, StringPrintfMisc) { + EXPECT_EQ("123hello w", StringPrintf("%3d%2s %1c", 123, "hello", 'w')); + EXPECT_EQ(L"123hello w", StringPrintf(L"%3d%2s %1c", 123, L"hello", 'w')); +} + +TEST(StringUtilTest, StringAppendfStringEmptyParam) { + std::string value("Hello"); + StringAppendF(&value, ""); + EXPECT_EQ("Hello", value); + + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L""); + EXPECT_EQ(L"Hello", valuew); +} + +TEST(StringUtilTest, StringAppendfEmptyString) { + std::string value("Hello"); + StringAppendF(&value, "%s", ""); + EXPECT_EQ("Hello", value); + + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L"%s", L""); + EXPECT_EQ(L"Hello", valuew); +} + +TEST(StringUtilTest, StringAppendfString) { + std::string value("Hello"); + StringAppendF(&value, " %s", "World"); + EXPECT_EQ("Hello World", value); + + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %s", L"World"); + EXPECT_EQ(L"Hello World", valuew); +} + +TEST(StringUtilTest, StringAppendfInt) { + std::string value("Hello"); + StringAppendF(&value, " %d", 123); + EXPECT_EQ("Hello 123", value); + + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %d", 123); + EXPECT_EQ(L"Hello 123", valuew); +} + +// Make sure that lengths exactly around the initial buffer size are handled +// correctly. +TEST(StringUtilTest, StringPrintfBounds) { + const int src_len = 1026; + char src[src_len]; + for (int i = 0; i < arraysize(src); i++) + src[i] = 'A'; + + wchar_t srcw[src_len]; + for (int i = 0; i < arraysize(srcw); i++) + srcw[i] = 'A'; + + for (int i = 1; i < 3; i++) { + src[src_len - i] = 0; + std::string out; + SStringPrintf(&out, "%s", src); + EXPECT_STREQ(src, out.c_str()); + + srcw[src_len - i] = 0; + std::wstring outw; + SStringPrintf(&outw, L"%s", srcw); + EXPECT_STREQ(srcw, outw.c_str()); + } +} + +// Test very large sprintfs that will cause the buffer to grow. +TEST(StringUtilTest, Grow) { + char src[1026]; + for (int i = 0; i < arraysize(src); i++) + src[i] = 'A'; + src[1025] = 0; + + char* fmt = "%sB%sB%sB%sB%sB%sB%s"; + + std::string out; + SStringPrintf(&out, fmt, src, src, src, src, src, src, src); + + char* ref = new char[320000]; + sprintf_s(ref, 320000, fmt, src, src, src, src, src, src, src); + + EXPECT_STREQ(ref, out.c_str()); + delete ref; +} + +// Test the boundary condition for the size of the string_util's +// internal buffer. +TEST(StringUtilTest, 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()); +} + +// sprintf in Visual Studio fails when given U+FFFF. This tests that the +// failure case is gracefuly handled. +TEST(StringUtilTest, Invalid) { + wchar_t invalid[2]; + invalid[0] = 0xffff; + invalid[1] = 0; + + std::wstring out; + SStringPrintf(&out, L"%s", invalid); + EXPECT_STREQ(L"", out.c_str()); +} + +// Test for SplitString +TEST(StringUtilTest, SplitString) { + std::vector<std::wstring> r; + + SplitString(L"a,b,c", L',', &r); + EXPECT_EQ(r.size(), 3); + 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); + EXPECT_EQ(r.size(), 3); + 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); + EXPECT_EQ(r.size(), 3); + 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(r.size(), 1); + EXPECT_EQ(r[0], L""); + r.clear(); + + SplitString(L"foo", L'*', &r); + EXPECT_EQ(r.size(), 1); + EXPECT_EQ(r[0], L"foo"); + r.clear(); + + SplitString(L"foo ,", L',', &r); + EXPECT_EQ(r.size(), 2); + EXPECT_EQ(r[0], L"foo"); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L",", L',', &r); + EXPECT_EQ(r.size(), 2); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L"\t\ta\t", L'\t', &r); + EXPECT_EQ(r.size(), 4); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"a"); + EXPECT_EQ(r[3], L""); + r.clear(); + + SplitStringDontTrim(L"\t\ta\t", L'\t', &r); + EXPECT_EQ(r.size(), 4); + 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); + EXPECT_EQ(r.size(), 2); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b\tcc"); + r.clear(); + + SplitStringDontTrim(L"\ta\t\nb\tcc", L'\n', &r); + EXPECT_EQ(r.size(), 2); + EXPECT_EQ(r[0], L"\ta\t"); + EXPECT_EQ(r[1], L"b\tcc"); + r.clear(); +} + +TEST(StringUtilTest, StartsWith) { + EXPECT_EQ(true, StartsWithASCII("javascript:url", "javascript", true)); + EXPECT_EQ(true, StartsWithASCII("javascript:url", "javascript", false)); + EXPECT_EQ(true, StartsWithASCII("JavaScript:url", "javascript", false)); + EXPECT_EQ(false, StartsWithASCII("java", "javascript", true)); + EXPECT_EQ(false, StartsWithASCII("java", "javascript", false)); +} + +TEST(StringUtilTest, GetStringFWithOffsets) { + std::vector<size_t> offsets; + + ReplaceStringPlaceholders(L"Hello, $1. Your number is $2.", L"1", L"2", + &offsets); + EXPECT_EQ(2, offsets.size()); + EXPECT_EQ(7, offsets[0]); + EXPECT_EQ(25, offsets[1]); + offsets.clear(); + + ReplaceStringPlaceholders(L"Hello, $2. Your number is $1.", L"1", L"2", + &offsets); + EXPECT_EQ(2, offsets.size()); + EXPECT_EQ(25, offsets[0]); + EXPECT_EQ(7, offsets[1]); + offsets.clear(); +} + +TEST(StringUtilTest, SplitStringAlongWhitespace) { + struct TestData { + const std::wstring input; + const int expected_result_count; + const std::wstring output1; + const std::wstring output2; + } data[] = { + { L"a", 1, L"a", L"" }, + { L" ", 0, L"", L"" }, + { L" a", 1, L"a", L"" }, + { L" ab ", 1, L"ab", L"" }, + { L" ab c", 2, L"ab", L"c" }, + { L" ab c ", 2, L"ab", L"c" }, + { L" ab cd", 2, L"ab", L"cd" }, + { L" ab cd ", 2, L"ab", L"cd" }, + { L" \ta\t", 1, L"a", L"" }, + { L" b\ta\t", 2, L"b", L"a" }, + { L" b\tat", 2, L"b", L"at" }, + { L"b\tat", 2, L"b", L"at" }, + { L"b\t at", 2, L"b", L"at" }, + }; + for (size_t i = 0; i < arraysize(data); ++i) { + std::vector<std::wstring> 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]); + } +} + +TEST(StringUtilTest, MatchPatternTest) { + EXPECT_EQ(MatchPattern(L"www.google.com", L"*.com"), true); + EXPECT_EQ(MatchPattern(L"www.google.com", L"*"), true); + EXPECT_EQ(MatchPattern(L"www.google.com", L"www*.g*.org"), false); + EXPECT_EQ(MatchPattern(L"Hello", L"H?l?o"), true); + EXPECT_EQ(MatchPattern(L"www.google.com", L"http://*)"), false); + EXPECT_EQ(MatchPattern(L"www.msn.com", L"*.COM"), false); + EXPECT_EQ(MatchPattern(L"Hello*1234", L"He??o\\*1*"), true); + EXPECT_EQ(MatchPattern(L"", L"*.*"), false); + EXPECT_EQ(MatchPattern(L"", L"*"), true); + EXPECT_EQ(MatchPattern(L"", L"?"), true); + EXPECT_EQ(MatchPattern(L"", L""), true); + EXPECT_EQ(MatchPattern(L"Hello", L""), false); + EXPECT_EQ(MatchPattern(L"Hello*", L"Hello*"), true); + EXPECT_EQ(MatchPattern("Hello*", "Hello*"), true); // narrow string +} + + diff --git a/base/string_util_win.cc b/base/string_util_win.cc new file mode 100644 index 0000000..6cad854 --- /dev/null +++ b/base/string_util_win.cc @@ -0,0 +1,124 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/string_util.h" + +#include <windows.h> +#include <string> +#include "unicode/numfmt.h" +#include "base/logging.h" + +// See WideToUTF8. +static std::string WideToMultiByte(const std::wstring& wide, UINT code_page) { + int wide_length = static_cast<int>(wide.length()); + if (wide_length == 0) + return std::string(); + + // compute the length of the buffer we'll need + int charcount = WideCharToMultiByte(code_page, 0, wide.data(), wide_length, + NULL, 0, NULL, NULL); + if (charcount == 0) + return std::string(); + + // convert + std::string mb; + WideCharToMultiByte(code_page, 0, wide.data(), wide_length, + WriteInto(&mb, charcount + 1), charcount, NULL, NULL); + + return mb; +} + +// Converts the given 8-bit string into a wide string, using the given +// code page. The code page identifier is one accepted by MultiByteToWideChar() +// +// Danger: do not assert in this function, as it is used by the assertion code. +// Doing so will cause an infinite loop. +static std::wstring MultiByteToWide(const std::string& mb, UINT code_page) { + if (mb.length() == 0) + return std::wstring(); + + // compute the length of the buffer + int charcount = MultiByteToWideChar(code_page, 0, mb.c_str(), -1, NULL, 0); + if (charcount == 0) + return std::wstring(); + + // convert + std::wstring wide; + MultiByteToWideChar(code_page, 0, mb.c_str(), -1, + WriteInto(&wide, charcount), charcount); + + return wide; +} + +// Wide <--> UTF-8 +std::string WideToUTF8(const std::wstring& wide) { + + return WideToMultiByte(wide, CP_UTF8); +} + +std::wstring UTF8ToWide(const std::string& utf8) { + return MultiByteToWide(utf8, CP_UTF8); +} + +// Wide <--> native multibyte +std::string WideToNativeMB(const std::wstring& wide) { + return WideToMultiByte(wide, CP_ACP); +} + +std::wstring NativeMBToWide(const std::string& native_mb) { + return MultiByteToWide(native_mb, CP_ACP); +} + +NumberFormat* NumberFormatSingleton() { + static NumberFormat* number_format = NULL; + if (!number_format) { + // Make sure we are thread-safe. + UErrorCode status = U_ZERO_ERROR; + NumberFormat* new_number_format = NumberFormat::createInstance(status); + if (U_FAILURE(status)) { + NOTREACHED(); + } + if (InterlockedCompareExchangePointer( + reinterpret_cast<PVOID*>(&number_format), new_number_format, NULL)) { + // The old value was non-NULL, so no replacement was done. Another + // thread did the initialization out from under us. + if (new_number_format) + delete new_number_format; + } + } + return number_format; +} + +int64 StringToInt64(const std::string& value) { + return _atoi64(value.c_str()); +} + +int64 StringToInt64(const std::wstring& value) { + return _wtoi64(value.c_str()); +} diff --git a/base/string_util_win.h b/base/string_util_win.h new file mode 100644 index 0000000..151a428 --- /dev/null +++ b/base/string_util_win.h @@ -0,0 +1,76 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_STRING_UTIL_WIN_H__ +#define BASE_STRING_UTIL_WIN_H__ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <wchar.h> + +inline bool StrCpy(char* dst, const char* src, size_t dst_size) { + return strcpy_s(dst, dst_size, src) == 0; +} + +inline bool StrNCpy(char* dst, const char* src, + size_t dst_size, size_t src_size) { + return strncpy_s(dst, dst_size, src, src_size) == 0; +} + +inline int StrNCaseCmp(const char* s1, const char* s2, size_t count) { + return _strnicmp(s1, s2, count); +} + +inline int VSNPrintF(char* buffer, size_t size, + const char* format, va_list arguments) { + int length = vsnprintf_s(buffer, size, size - 1, format, arguments); + if (length < 0) + return _vscprintf(format, arguments); + return length; +} + +inline bool WcsCpy(wchar_t* dst, const wchar_t* src, size_t dst_size) { + return wcscpy_s(dst, dst_size, src) == 0; +} + +inline bool WcsNCpy(wchar_t* dst, const wchar_t* src, + size_t dst_size, size_t src_size) { + return wcsncpy_s(dst, dst_size, src, src_size) == 0; +} + +inline int VSWPrintF(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + int length = _vsnwprintf_s(buffer, size, size - 1, format, arguments); + if (length < 0) + return _vscwprintf(format, arguments); + return length; +} + +#endif // BASE_STRING_UTIL_WIN_H__ diff --git a/base/task.h b/base/task.h new file mode 100644 index 0000000..a964a17 --- /dev/null +++ b/base/task.h @@ -0,0 +1,725 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_TASK_H__ +#define BASE_TASK_H__ + +#include <set> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/non_thread_safe.h" +#include "base/revocable_store.h" +#include "base/tracked.h" +#include "base/tuple.h" + +//------------------------------------------------------------------------------ +// Base class of Task, where we store info to help MessageLoop handle PostTask() +// elements of Task processing. + +class Task; + +class MessageLoopOwnable : public tracked_objects::Tracked { + public: + MessageLoopOwnable() { Reset(); } + virtual ~MessageLoopOwnable() {} + + // Use this method to adjust the priority given to a task by MessageLoop. + void set_priority(int priority) { priority_ = priority; } + int priority() const { return priority_; } + + // Change whether this task will run in nested message loops. + void set_nestable(bool nestable) { nestable_ = nestable; } + bool nestable() { return nestable_; } + + + protected: + // If a derived class wishes to re-use this instance, then it should override + // this method. This method is called by MessageLoop after processing a task + // that was submitted to PostTask() or PostDelayedTask(). As seen, by default + // it deletes the task, but the derived class can change this behaviour and + // recycle (re-use) it. Be sure to call Reset() if you recycle it! + virtual void RecycleOrDelete() { delete this; } + + // Call this method if you are trying to recycle a Task. Note that only + // derived classes should attempt this feat, as a replacement for creating a + // new instance. + void Reset() { + posted_task_delay_ = -1; + priority_ = 0; + next_task_ = NULL; + nestable_ = true; + } + + private: + friend class TimerManager; // To check is_owned_by_message_loop(). + friend class MessageLoop; // To maintain posted_task_delay(). + friend class WorkerPool; // To release the task. + + // Access methods used ONLY by friends in MessageLoop and TimerManager + int posted_task_delay() const { return posted_task_delay_; } + bool is_owned_by_message_loop() const { return 0 <= posted_task_delay_; } + void set_posted_task_delay(int delay) { posted_task_delay_ = delay; } + + Task* next_task() const { return next_task_; } + void set_next_task(Task* next) { next_task_ = next; } + + // Priority for execution by MessageLoop. 0 is default. Higher means run + // sooner, and lower (including negative) means run less soon. + int priority_; + + // Slot to hold delay if the task was passed to PostTask(). If it was not + // passed to PostTask, then the delay is negative (the default). + int posted_task_delay_; + + // When tasks are collected into a queue by MessageLoop, this member is used + // to form a null terminated list. + Task* next_task_; + + // A nestable task will run in nested message loops, otherwise it will run + // only in the top level message loop. + bool nestable_; + + DISALLOW_EVIL_CONSTRUCTORS(MessageLoopOwnable); +}; + + +// Task ------------------------------------------------------------------------ +// +// A task is a generic runnable thingy, usually used for running code on a +// different thread or for scheduling future tasks off of the message loop. + +class Task : public MessageLoopOwnable { + public: + Task() {} + virtual ~Task() {} + + // Tasks are automatically deleted after Run is called. + virtual void Run() = 0; +}; + +class CancelableTask : public Task { + public: + // Not all tasks support cancellation. + virtual void Cancel() = 0; +}; + +// Scoped Factories ------------------------------------------------------------ +// +// These scoped factory objects can be used by non-refcounted objects to safely +// place tasks in a message loop. Each factory guarantees that the tasks it +// produces will not run after the factory is destroyed. Commonly, factories +// are declared as class members, so the class' tasks will automatically cancel +// when the class instance is destroyed. +// +// Exampe Usage: +// +// class MyClass { +// private: +// // This factory will be used to schedule invocations of SomeMethod. +// ScopedRunnableMethodFactory<MyClass> some_method_factory_; +// +// public: +// // It is safe to suppress warning 4355 here. +// MyClass() : some_method_factory_(this) { } +// +// void SomeMethod() { +// // If this function might be called directly, you might want to revoke +// // any outstanding runnable methods scheduled to call it. If it's not +// // referenced other than by the factory, this is unnecessary. +// some_method_factory_.RevokeAll(); +// ... +// } +// +// void ScheduleSomeMethod() { +// // If you'd like to only only have one pending task at a time, test for +// // |empty| before manufacturing another task. +// if (!some_method_factory_.empty()) +// return; +// +// // The factories are not thread safe, so always invoke on +// // |MessageLoop::current()|. +// MessageLoop::current()->PostTask(FROM_HERE, +// some_method_factory_.NewRunnableMethod(&MyClass::SomeMethod), +// kSomeMethodDelayMS); +// } +// }; + +// A ScopedTaskFactory produces tasks of type |TaskType| and prevents them from +// running after it is destroyed. +template<class TaskType> +class ScopedTaskFactory : public RevocableStore { + public: + ScopedTaskFactory() { } + + // Create a new task. + inline TaskType* NewTask() { + return new TaskWrapper(this); + } + + class TaskWrapper : public TaskType, public NonThreadSafe { + public: + explicit TaskWrapper(RevocableStore* store) : revocable_(store) { } + + virtual void Run() { + if (!revocable_.revoked()) + TaskType::Run(); + } + + private: + Revocable revocable_; + + DISALLOW_EVIL_CONSTRUCTORS(TaskWrapper); + }; + + private: + DISALLOW_EVIL_CONSTRUCTORS(ScopedTaskFactory); +}; + +// A ScopedRunnableMethodFactory creates runnable methods for a specified +// object. This is particularly useful for generating callbacks for +// non-reference counted objects when the factory is a member of the object. +template<class T> +class ScopedRunnableMethodFactory : public RevocableStore { + public: + explicit ScopedRunnableMethodFactory(T* object) : object_(object) { } + + template <class Method> + inline Task* NewRunnableMethod(Method method) { + typedef typename ScopedTaskFactory<RunnableMethod< + Method, Tuple0> >::TaskWrapper TaskWrapper; + + TaskWrapper* task = new TaskWrapper(this); + task->Init(object_, method, MakeTuple()); + return task; + } + + template <class Method, class A> + inline Task* NewRunnableMethod(Method method, const A& a) { + typedef typename ScopedTaskFactory<RunnableMethod< + Method, Tuple1<A> > >::TaskWrapper TaskWrapper; + + TaskWrapper* task = new TaskWrapper(this); + task->Init(object_, method, MakeTuple(a)); + return task; + } + + template <class Method, class A, class B> + inline Task* NewRunnableMethod(Method method, const A& a, const B& b) { + typedef typename ScopedTaskFactory<RunnableMethod< + Method, Tuple2<A, B> > >::TaskWrapper TaskWrapper; + + TaskWrapper* task = new TaskWrapper(this); + task->Init(object_, method, MakeTuple(a, b)); + return task; + } + + template <class Method, class A, class B, class C> + inline Task* NewRunnableMethod(Method method, + const A& a, + const B& b, + const C& c) { + typedef typename ScopedTaskFactory<RunnableMethod< + Method, Tuple3<A, B, C> > >::TaskWrapper TaskWrapper; + + TaskWrapper* task = new TaskWrapper(this); + task->Init(object_, method, MakeTuple(a, b, c)); + return task; + } + + template <class Method, class A, class B, class C, class D> + inline Task* NewRunnableMethod(Method method, + const A& a, + const B& b, + const C& c, + const D& d) { + typedef typename ScopedTaskFactory<RunnableMethod< + Method, Tuple4<A, B, C, D> > >::TaskWrapper TaskWrapper; + + TaskWrapper* task = new TaskWrapper(this); + task->Init(object_, method, MakeTuple(a, b, c, d)); + return task; + } + + template <class Method, class A, class B, class C, class D, class E> + inline Task* NewRunnableMethod(Method method, + const A& a, + const B& b, + const C& c, + const D& d, + const E& e) { + typedef typename ScopedTaskFactory<RunnableMethod< + Method, Tuple5<A, B, C, D, E> > >::TaskWrapper TaskWrapper; + + TaskWrapper* task = new TaskWrapper(this); + task->Init(object_, method, MakeTuple(a, b, c, d, e)); + return task; + } + + protected: + template <class Method, class Params> + class RunnableMethod : public Task { + public: + RunnableMethod() { } + + void Init(T* obj, Method meth, const Params& params) { + obj_ = obj; + meth_ = meth; + params_ = params; + } + + virtual void Run() { DispatchToMethod(obj_, meth_, params_); } + + private: + T* obj_; + Method meth_; + Params params_; + + DISALLOW_EVIL_CONSTRUCTORS(RunnableMethod); + }; + + private: + T* object_; + + DISALLOW_EVIL_CONSTRUCTORS(ScopedRunnableMethodFactory); +}; + +// General task implementations ------------------------------------------------ + +// Task to delete an object +template<class T> +class DeleteTask : public CancelableTask { + public: + explicit DeleteTask(T* obj) : obj_(obj) { + set_nestable(false); + } + virtual void Run() { + delete obj_; + } + virtual void Cancel() { + obj_ = NULL; + } + private: + T* obj_; +}; + +// Task to Release() an object +template<class T> +class ReleaseTask : public CancelableTask { + public: + explicit ReleaseTask(T* obj) : obj_(obj) { + set_nestable(false); + } + virtual void Run() { + if (obj_) + obj_->Release(); + } + virtual void Cancel() { + obj_ = NULL; + } + private: + T* obj_; +}; + +// RunnableMethodTraits -------------------------------------------------------- +// +// This traits-class is used by RunnableMethod to manage the lifetime of the +// callee object. By default, it is assumed that the callee supports AddRef +// and Release methods. A particular class can specialize this template to +// define other lifetime management. For example, if the callee is known to +// live longer than the RunnableMethod object, then a RunnableMethodTraits +// struct could be defined with empty RetainCallee and ReleaseCallee methods. + +template <class T> +struct RunnableMethodTraits { + static void RetainCallee(T* obj) { + obj->AddRef(); + } + static void ReleaseCallee(T* obj) { + obj->Release(); + } +}; + +// RunnableMethod and RunnableFunction ----------------------------------------- +// +// Runnable methods are a type of task that call a function on an object when +// they are run. We implement both an object and a set of NewRunnableMethod and +// NewRunnableFunction functions for convenience. These functions are +// overloaded and will infer the template types, simplifying calling code. +// +// The template definitions all use the following names: +// T - the class type of the object you're supplying +// this is not needed for the Static version of the call +// Method/Function - the signature of a pointer to the method or function you +// want to call +// Param - the parameter(s) to the method, possibly packed as a Tuple +// A - the first parameter (if any) to the method +// B - the second parameter (if any) to the mathod +// +// Put these all together and you get an object that can call a method whose +// signature is: +// R T::MyFunction([A[, B]]) +// +// Usage: +// PostTask(FROM_HERE, NewRunnableMethod(object, &Object::method[, a[, b]]) +// PostTask(FROM_HERE, NewRunnableFunction(&function[, a[, b]]) + +// RunnableMethod and NewRunnableMethod implementation ------------------------- + +template <class T, class Method, class Params> +class RunnableMethod : public CancelableTask, + public RunnableMethodTraits<T> { + public: + RunnableMethod(T* obj, Method meth, const Params& params) + : obj_(obj), meth_(meth), params_(params) { + RetainCallee(obj_); + } + ~RunnableMethod() { + ReleaseCallee(); + } + + virtual void Run() { + if (obj_) + DispatchToMethod(obj_, meth_, params_); + } + + virtual void Cancel() { + ReleaseCallee(); + } + + private: + void ReleaseCallee() { + if (obj_) { + RunnableMethodTraits<T>::ReleaseCallee(obj_); + obj_ = NULL; + } + } + + T* obj_; + Method meth_; + Params params_; +}; + +template <class T, class Method> +inline CancelableTask* NewRunnableMethod(T* object, Method method) { + return new RunnableMethod<T, Method, Tuple0>(object, method, MakeTuple()); +} + +template <class T, class Method, class A> +inline CancelableTask* NewRunnableMethod(T* object, Method method, const A& a) { + return new RunnableMethod<T, Method, Tuple1<A> >(object, method, MakeTuple(a)); +} + +template <class T, class Method, class A, class B> +inline CancelableTask* NewRunnableMethod(T* object, Method method, +const A& a, const B& b) { + return new RunnableMethod<T, Method, Tuple2<A, B> >(object, method, + MakeTuple(a, b)); +} + +template <class T, class Method, class A, class B, class C> +inline CancelableTask* NewRunnableMethod(T* object, Method method, + const A& a, const B& b, const C& c) { + return new RunnableMethod<T, Method, Tuple3<A, B, C> >(object, method, + MakeTuple(a, b, c)); +} + +template <class T, class Method, class A, class B, class C, class D> +inline CancelableTask* NewRunnableMethod(T* object, Method method, + const A& a, const B& b, + const C& c, const D& d) { + return new RunnableMethod<T, Method, Tuple4<A, B, C, D> >(object, method, + MakeTuple(a, b, + c, d)); +} + +template <class T, class Method, class A, class B, class C, class D, class E> +inline CancelableTask* NewRunnableMethod(T* object, Method method, + const A& a, const B& b, + const C& c, const D& d, const E& e) { + return new RunnableMethod<T, + Method, + Tuple5<A, B, C, D, E> >(object, + method, + MakeTuple(a, b, c, d, e)); +} + +// RunnableFunction and NewRunnableFunction implementation --------------------- + +template <class Function, class Params> +class RunnableFunction : public CancelableTask { + public: + RunnableFunction(Function function, const Params& params) + : function_(function), params_(params) { + } + + ~RunnableFunction() { + } + + virtual void Run() { + if (function_) + DispatchToFunction(function_, params_); + } + + virtual void Cancel() { + } + + private: + Function function_; + Params params_; +}; + +template <class Function> +inline CancelableTask* NewRunnableFunction(Function function) { + return new RunnableFunction<Function, Tuple0>(function, MakeTuple()); +} + +template <class Function, class A> +inline CancelableTask* NewRunnableFunction(Function function, const A& a) { + return new RunnableFunction<Function, Tuple1<A> >(function, MakeTuple(a)); +} + +template <class Function, class A, class B> +inline CancelableTask* NewRunnableFunction(Function function, + const A& a, const B& b) { + return new RunnableFunction<Function, Tuple2<A, B> >(function, MakeTuple(a, b)); +} + +template <class Function, class A, class B, class C> +inline CancelableTask* NewRunnableFunction(Function function, + const A& a, const B& b, + const C& c) { + return new RunnableFunction<Function, Tuple3<A, B, C> >(function, + MakeTuple(a, b, c)); +} + +template <class Function, class A, class B, class C, class D> +inline CancelableTask* NewRunnableFunction(Function function, + const A& a, const B& b, + const C& c, const D& d) { + return new RunnableFunction<Function, Tuple4<A, B, C, D> >(function, + MakeTuple(a, b, + c, d)); +} + +template <class Function, class A, class B, class C, class D, class E> +inline CancelableTask* NewRunnableFunction(Function function, + const A& a, const B& b, + const C& c, const D& d, + const E& e) { + return new RunnableFunction<Function, Tuple5<A, B, C, D, E> >(function, + MakeTuple(a, b, + c, d, + e)); +} + +// Callback -------------------------------------------------------------------- +// +// A Callback is like a Task but with unbound parameters. It is basically an +// object-oriented function pointer. +// +// Callbacks are designed to work with Tuples. A set of helper functions and +// classes is provided to hide the Tuple details from the consumer. Client +// code will generally work with the CallbackRunner base class, which merely +// provides a Run method and is returned by the New* functions. This allows +// users to not care which type of class implements the callback, only that it +// has a certain number and type of arguments. +// +// The implementation of this is done by CallbackImpl, which inherits +// CallbackStorage to store the data. This allows the storage of the data +// (requiring the class type T) to be hidden from users, who will want to call +// this regardless of the implementor's type T. +// +// Note that callbacks currently have no facility for cancelling or abandoning +// them. We currently handle this at a higher level for cases where this is +// necessary. The pointer in a callback must remain valid until the callback +// is made. +// +// Like Task, the callback executor is responsible for deleting the callback +// pointer once the callback has executed. +// +// Example client usage: +// void Object::DoStuff(int, string); +// Callback2<int, string>::Type* callback = +// NewCallback(obj, &Object::DoStuff); +// callback->Run(5, string("hello")); +// delete callback; +// or, equivalently, using tuples directly: +// CallbackRunner<Tuple2<int, string> >* callback = +// NewCallback(obj, &Object::DoStuff); +// callback->RunWithParams(MakeTuple(5, string("hello"))); + +// Base for all Callbacks that handles storage of the pointers. +template <class T, typename Method> +class CallbackStorage { + public: + CallbackStorage(T* obj, Method meth) : obj_(obj), meth_(meth) { + } + + protected: + T* obj_; + Method meth_; +}; + +// Interface that is exposed to the consumer, that does the actual calling +// of the method. +template <typename Params> +class CallbackRunner { + public: + typedef Params TupleType; + + virtual ~CallbackRunner() {} + virtual void RunWithParams(const Params& params) = 0; + + // Convenience functions so callers don't have to deal with Tuples. + inline void Run() { + RunWithParams(Tuple0()); + } + + template <typename Arg1> + inline void Run(const Arg1& a) { + RunWithParams(Params(a)); + } + + template <typename Arg1, typename Arg2> + inline void Run(const Arg1& a, const Arg2& b) { + RunWithParams(Params(a, b)); + } + + template <typename Arg1, typename Arg2, typename Arg3> + inline void Run(const Arg1& a, const Arg2& b, const Arg3& c) { + RunWithParams(Params(a, b, c)); + } + + template <typename Arg1, typename Arg2, typename Arg3, typename Arg4> + inline void Run(const Arg1& a, const Arg2& b, const Arg3& c, const Arg4& d) { + RunWithParams(Params(a, b, c, d)); + } + + template <typename Arg1, typename Arg2, typename Arg3, + typename Arg4, typename Arg5> + inline void Run(const Arg1& a, const Arg2& b, const Arg3& c, + const Arg4& d, const Arg5& e) { + RunWithParams(Params(a, b, c, d, e)); + } +}; + +template <class T, typename Method, typename Params> +class CallbackImpl : public CallbackStorage<T, Method>, + public CallbackRunner<Params> { + public: + CallbackImpl(T* obj, Method meth) : CallbackStorage<T, Method>(obj, meth) { + } + virtual void RunWithParams(const Params& params) { + // use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + DispatchToMethod(this->obj_, this->meth_, params); + } +}; + +// 0-arg implementation +struct Callback0 { + typedef CallbackRunner<Tuple0> Type; +}; + +template <class T> +typename Callback0::Type* NewCallback(T* object, void (T::*method)()) { + return new CallbackImpl<T, void (T::*)(), Tuple0 >(object, method); +} + +// 1-arg implementation +template <typename Arg1> +struct Callback1 { + typedef CallbackRunner<Tuple1<Arg1> > Type; +}; + +template <class T, typename Arg1> +typename Callback1<Arg1>::Type* NewCallback(T* object, void (T::*method)(Arg1)) { + return new CallbackImpl<T, void (T::*)(Arg1), Tuple1<Arg1> >(object, method); +} + +// 2-arg implementation +template <typename Arg1, typename Arg2> +struct Callback2 { + typedef CallbackRunner<Tuple2<Arg1, Arg2> > Type; +}; + +template <class T, typename Arg1, typename Arg2> +typename Callback2<Arg1, Arg2>::Type* NewCallback( + T* object, + void (T::*method)(Arg1, Arg2)) { + return new CallbackImpl<T, void (T::*)(Arg1, Arg2), + Tuple2<Arg1, Arg2> >(object, method); +} + +// 3-arg implementation +template <typename Arg1, typename Arg2, typename Arg3> +struct Callback3 { + typedef CallbackRunner<Tuple3<Arg1, Arg2, Arg3> > Type; +}; + +template <class T, typename Arg1, typename Arg2, typename Arg3> +typename Callback3<Arg1, Arg2, Arg3>::Type* NewCallback( + T* object, + void (T::*method)(Arg1, Arg2, Arg3)) { + return new CallbackImpl<T, void (T::*)(Arg1, Arg2, Arg3), + Tuple3<Arg1, Arg2, Arg3> >(object, method); +} + +// 4-arg implementation +template <typename Arg1, typename Arg2, typename Arg3, typename Arg4> +struct Callback4 { + typedef CallbackRunner<Tuple4<Arg1, Arg2, Arg3, Arg4> > Type; +}; + +template <class T, typename Arg1, typename Arg2, typename Arg3, typename Arg4> +typename Callback4<Arg1, Arg2, Arg3, Arg4>::Type* NewCallback( + T* object, + void (T::*method)(Arg1, Arg2, Arg3, Arg4)) { + return new CallbackImpl<T, void (T::*)(Arg1, Arg2, Arg3, Arg4), + Tuple4<Arg1, Arg2, Arg3, Arg4> >(object, method); +} + +// 5-arg implementation +template <typename Arg1, typename Arg2, typename Arg3, + typename Arg4, typename Arg5> +struct Callback5 { + typedef CallbackRunner<Tuple5<Arg1, Arg2, Arg3, Arg4, Arg5> > Type; +}; + +template <class T, typename Arg1, typename Arg2, + typename Arg3, typename Arg4, typename Arg5> +typename Callback5<Arg1, Arg2, Arg3, Arg4, Arg5>::Type* NewCallback( + T* object, + void (T::*method)(Arg1, Arg2, Arg3, Arg4, Arg5)) { + return new CallbackImpl<T, void (T::*)(Arg1, Arg2, Arg3, Arg4, Arg5), + Tuple5<Arg1, Arg2, Arg3, Arg4, Arg5> >(object, method); +} + +#endif // BASE_TASK_H__ diff --git a/base/test_suite.h b/base/test_suite.h new file mode 100644 index 0000000..a021339 --- /dev/null +++ b/base/test_suite.h @@ -0,0 +1,115 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_TEST_SUITE_H__ +#define BASE_TEST_SUITE_H__ + +// Defines a basic test suite framework for running gtest based tests. You can +// instantiate this class in your main function and call its Run method to run +// any gtest based tests that are linked into your executable. + +#include <windows.h> + +#include "base/command_line.h" +#include "base/debug_on_start.h" +#include "base/icu_util.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/multiprocess_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TestSuite { + public: + TestSuite(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + } + + virtual ~TestSuite() { + // Flush any remaining messages. This ensures that any accumulated Task + // objects get destroyed before we exit, which avoids noise in purify + // leak-test results. + message_loop_.Quit(); + message_loop_.Run(); + } + + int Run() { + Initialize(); + + // Check to see if we are being run as a client process. + std::wstring client_func = + parsed_command_line_.GetSwitchValue(kRunClientProcess); + if (!client_func.empty()) { + // Convert our function name to a usable string for GetProcAddress. + std::string func_name(client_func.begin(), client_func.end()); + + // Get our module handle and search for an exported function + // which we can use as our client main. + MultiProcessTest::ChildFunctionPtr func = + reinterpret_cast<MultiProcessTest::ChildFunctionPtr>( + GetProcAddress(GetModuleHandle(NULL), func_name.c_str())); + if (func) + return func(); + return -1; + } + return RUN_ALL_TESTS(); + } + + protected: + // All fatal log messages (e.g. DCHECK failures) imply unit test failures + static void UnitTestAssertHandler(const std::string& str) { + FAIL() << str; + } + + // Disable crash dialogs so that it doesn't gum up the buildbot + virtual void SuppressErrorDialogs() { + UINT new_flags = SEM_FAILCRITICALERRORS | + SEM_NOGPFAULTERRORBOX | + SEM_NOOPENFILEERRORBOX; + + // Preserve existing error mode, as discussed at http://t/dmea + UINT existing_flags = SetErrorMode(new_flags); + SetErrorMode(existing_flags | new_flags); + } + + virtual void Initialize() { + // In some cases, we do not want to see standard error dialogs. + if (!IsDebuggerPresent() && + !parsed_command_line_.HasSwitch(L"show-error-dialogs")) { + SuppressErrorDialogs(); + logging::SetLogAssertHandler(UnitTestAssertHandler); + } + + icu_util::Initialize(); + } + + CommandLine parsed_command_line_; + MessageLoop message_loop_; +}; + +#endif // BASE_TEST_SUITE_H__ diff --git a/base/third_party/nspr/README.google b/base/third_party/nspr/README.google new file mode 100644 index 0000000..7775d60 --- /dev/null +++ b/base/third_party/nspr/README.google @@ -0,0 +1,2 @@ +The original code is the Netscape Portable Runtime (NSPR), licensed under +the MPL/GPL/LGPL tri-license (http://www.mozilla.org/MPL/). diff --git a/base/third_party/nspr/prcpucfg.h b/base/third_party/nspr/prcpucfg.h new file mode 100644 index 0000000..ad1aec4 --- /dev/null +++ b/base/third_party/nspr/prcpucfg.h @@ -0,0 +1,41 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_THIRD_PARTY_NSPR_PRCPUCFG_H__ +#define BASE_THIRD_PARTY_NSPR_PRCPUCFG_H__ + +#if defined(WIN32) +#include "base/third_party/nspr/prcpucfg_win.h" +#elif defined(__APPLE__) +#include "base/third_party/nspr/prcpucfg_mac.h" +#else +#error Provide a prcpucfg.h appropriate for your platform +#endif + +#endif // BASE_THIRD_PARTY_NSPR_PRCPUCFG_H__ diff --git a/base/third_party/nspr/prcpucfg_mac.h b/base/third_party/nspr/prcpucfg_mac.h new file mode 100644 index 0000000..dc7e0e0 --- /dev/null +++ b/base/third_party/nspr/prcpucfg_mac.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_UNIX +#define XP_UNIX +#endif + +#define PR_AF_INET6 30 /* same as AF_INET6 */ + +#if defined(i386) +#undef IS_BIG_ENDIAN +#define IS_LITTLE_ENDIAN 1 +#else +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 +#endif + +#define HAVE_LONG_LONG +#undef HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS 1 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 +#define PR_BITS_PER_DWORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */ + diff --git a/base/third_party/nspr/prcpucfg_win.h b/base/third_party/nspr/prcpucfg_win.h new file mode 100644 index 0000000..026258b --- /dev/null +++ b/base/third_party/nspr/prcpucfg_win.h @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_PC +#define XP_PC +#endif + +#ifndef WIN32 +#define WIN32 +#endif + +#ifndef WIN95 +#define WIN95 +#endif + +#define PR_AF_INET6 23 /* same as AF_INET6 */ + +#if defined(_M_IX86) || defined(_X86_) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_DOUBLE 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_WORD 32 +#define PR_BITS_PER_DWORD 64 +#define PR_BITS_PER_DOUBLE 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_WORD_LOG2 5 +#define PR_BITS_PER_DWORD_LOG2 6 +#define PR_BITS_PER_DOUBLE_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_WORD 4 +#define PR_ALIGN_OF_DWORD 8 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 2 + +#elif defined(_ALPHA_) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 + +#elif defined(_AMD64_) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_DOUBLE 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_WORD 64 +#define PR_BITS_PER_DWORD 64 +#define PR_BITS_PER_DOUBLE 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_WORD_LOG2 6 +#define PR_BITS_PER_DWORD_LOG2 6 +#define PR_BITS_PER_DOUBLE_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_WORD 8 +#define PR_ALIGN_OF_DWORD 8 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(_IA64_) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_DOUBLE 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_WORD 64 +#define PR_BITS_PER_DWORD 64 +#define PR_BITS_PER_DOUBLE 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_WORD_LOG2 6 +#define PR_BITS_PER_DWORD_LOG2 6 +#define PR_BITS_PER_DOUBLE_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_WORD 8 +#define PR_ALIGN_OF_DWORD 8 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#else /* defined(_M_IX86) || defined(_X86_) */ + +#error unknown processor architecture + +#endif /* defined(_M_IX86) || defined(_X86_) */ + +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG +#endif + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */ diff --git a/base/third_party/nspr/prtime.cc b/base/third_party/nspr/prtime.cc new file mode 100644 index 0000000..a5e851c --- /dev/null +++ b/base/third_party/nspr/prtime.cc @@ -0,0 +1,838 @@ +/* +* Portions are Copyright (C) 2007 Google Inc +* +* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is the Netscape Portable Runtime (NSPR). +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 1998-2000 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** +* +*/ + +/* + * prtime.cc -- + * NOTE: The original nspr file name is prtime.c + * + * NSPR date and time functions + * + * CVS revision 3.31 + */ + +/* +* The following functions were copied from the NSPR prtime.c file. +* PR_ParseTimeString +* PR_ImplodeTime +* This was modified to use the Win32 SYSTEMTIME/FILETIME structures +* and the timezone offsets are applied to the FILETIME structure. +* All types and defines have been defined in the base/third_party/prtime.h file +* These have been copied from the following nspr files. We have only copied over +* the types we need. +* 1. prtime.h +* 2. prtypes.h +* 3. prlong.h +*/ + +#include <windows.h> +#include <time.h> +#include "base/third_party/nspr/prtime.h" + +/* + *------------------------------------------------------------------------ + * + * PR_ImplodeTime -- + * + * Cf. time_t mktime(struct tm *tp) + * Note that 1 year has < 2^25 seconds. So an PRInt32 is large enough. + * + *------------------------------------------------------------------------ + */ +PRTime +PR_ImplodeTime(const PRExplodedTime *exploded) +{ + // Create the system struct representing our exploded time. + SYSTEMTIME st = {0}; + FILETIME ft = {0}; + ULARGE_INTEGER uli = {0}; + + st.wYear = exploded->tm_year; + st.wMonth = exploded->tm_month + 1; + st.wDayOfWeek = exploded->tm_wday; + st.wDay = exploded->tm_mday; + st.wHour = exploded->tm_hour; + st.wMinute = exploded->tm_min; + st.wSecond = exploded->tm_sec; + st.wMilliseconds = exploded->tm_usec/1000; + // Convert to FILETIME. + if (!SystemTimeToFileTime(&st, &ft)) { + NOTREACHED() << "Unable to convert time"; + return 0; + } + // Apply offsets. + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + // From second to 100-ns + uli.QuadPart -= + (exploded->tm_params.tp_gmt_offset + + exploded->tm_params.tp_dst_offset) * 10000000i64; // 7 zeros + // Convert to PRTime + uli.QuadPart -= 116444736000000000i64; // from Windows epoch to NSPR epoch + uli.QuadPart /= 10; // from 100-nanosecond to microsecond + return (PRTime)uli.QuadPart; +} + +/* + * The following code implements PR_ParseTimeString(). It is based on + * ns/lib/xp/xp_time.c, revision 1.25, by Jamie Zawinski <jwz@netscape.com>. + */ + +/* + * We only recognize the abbreviations of a small subset of time zones + * in North America, Europe, and Japan. + * + * PST/PDT: Pacific Standard/Daylight Time + * MST/MDT: Mountain Standard/Daylight Time + * CST/CDT: Central Standard/Daylight Time + * EST/EDT: Eastern Standard/Daylight Time + * AST: Atlantic Standard Time + * NST: Newfoundland Standard Time + * GMT: Greenwich Mean Time + * BST: British Summer Time + * MET: Middle Europe Time + * EET: Eastern Europe Time + * JST: Japan Standard Time + */ + +typedef enum +{ + TT_UNKNOWN, + + TT_SUN, TT_MON, TT_TUE, TT_WED, TT_THU, TT_FRI, TT_SAT, + + TT_JAN, TT_FEB, TT_MAR, TT_APR, TT_MAY, TT_JUN, + TT_JUL, TT_AUG, TT_SEP, TT_OCT, TT_NOV, TT_DEC, + + TT_PST, TT_PDT, TT_MST, TT_MDT, TT_CST, TT_CDT, TT_EST, TT_EDT, + TT_AST, TT_NST, TT_GMT, TT_BST, TT_MET, TT_EET, TT_JST +} TIME_TOKEN; + +/* + * This parses a time/date string into a PRTime + * (microseconds after "1-Jan-1970 00:00:00 GMT"). + * It returns PR_SUCCESS on success, and PR_FAILURE + * if the time/date string can't be parsed. + * + * Many formats are handled, including: + * + * 14 Apr 89 03:20:12 + * 14 Apr 89 03:20 GMT + * Fri, 17 Mar 89 4:01:33 + * Fri, 17 Mar 89 4:01 GMT + * Mon Jan 16 16:12 PDT 1989 + * Mon Jan 16 16:12 +0130 1989 + * 6 May 1992 16:41-JST (Wednesday) + * 22-AUG-1993 10:59:12.82 + * 22-AUG-1993 10:59pm + * 22-AUG-1993 12:59am + * 22-AUG-1993 12:59 PM + * Friday, August 04, 1995 3:54 PM + * 06/21/95 04:24:34 PM + * 20/06/95 21:07 + * 95-06-08 19:32:48 EDT + * + * If the input string doesn't contain a description of the timezone, + * we consult the `default_to_gmt' to decide whether the string should + * be interpreted relative to the local time zone (PR_FALSE) or GMT (PR_TRUE). + * The correct value for this argument depends on what standard specified + * the time string which you are parsing. + */ + +PRStatus +PR_ParseTimeString( + const char *string, + PRBool default_to_gmt, + PRTime *result) +{ + PRExplodedTime tm; + TIME_TOKEN dotw = TT_UNKNOWN; + TIME_TOKEN month = TT_UNKNOWN; + TIME_TOKEN zone = TT_UNKNOWN; + int zone_offset = -1; + int date = -1; + PRInt32 year = -1; + int hour = -1; + int min = -1; + int sec = -1; + + const char *rest = string; + +#ifdef DEBUG + int iterations = 0; +#endif + + PR_ASSERT(string && result); + if (!string || !result) return PR_FAILURE; + + while (*rest) + { + +#ifdef DEBUG + if (iterations++ > 1000) + { + PR_ASSERT(0); + return PR_FAILURE; + } +#endif + + switch (*rest) + { + case 'a': case 'A': + if (month == TT_UNKNOWN && + (rest[1] == 'p' || rest[1] == 'P') && + (rest[2] == 'r' || rest[2] == 'R')) + month = TT_APR; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 's') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_AST; + else if (month == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'g' || rest[2] == 'G')) + month = TT_AUG; + break; + case 'b': case 'B': + if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_BST; + break; + case 'c': case 'C': + if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_CDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_CST; + break; + case 'd': case 'D': + if (month == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'c' || rest[2] == 'C')) + month = TT_DEC; + break; + case 'e': case 'E': + if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_EDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_EET; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_EST; + break; + case 'f': case 'F': + if (month == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'b' || rest[2] == 'B')) + month = TT_FEB; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'r' || rest[1] == 'R') && + (rest[2] == 'i' || rest[2] == 'I')) + dotw = TT_FRI; + break; + case 'g': case 'G': + if (zone == TT_UNKNOWN && + (rest[1] == 'm' || rest[1] == 'M') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_GMT; + break; + case 'j': case 'J': + if (month == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 'n' || rest[2] == 'N')) + month = TT_JAN; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_JST; + else if (month == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'l' || rest[2] == 'L')) + month = TT_JUL; + else if (month == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'n' || rest[2] == 'N')) + month = TT_JUN; + break; + case 'm': case 'M': + if (month == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 'r' || rest[2] == 'R')) + month = TT_MAR; + else if (month == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 'y' || rest[2] == 'Y')) + month = TT_MAY; + else if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_MDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_MET; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'o' || rest[1] == 'O') && + (rest[2] == 'n' || rest[2] == 'N')) + dotw = TT_MON; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_MST; + break; + case 'n': case 'N': + if (month == TT_UNKNOWN && + (rest[1] == 'o' || rest[1] == 'O') && + (rest[2] == 'v' || rest[2] == 'V')) + month = TT_NOV; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_NST; + break; + case 'o': case 'O': + if (month == TT_UNKNOWN && + (rest[1] == 'c' || rest[1] == 'C') && + (rest[2] == 't' || rest[2] == 'T')) + month = TT_OCT; + break; + case 'p': case 'P': + if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_PDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_PST; + break; + case 's': case 'S': + if (dotw == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 't' || rest[2] == 'T')) + dotw = TT_SAT; + else if (month == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'p' || rest[2] == 'P')) + month = TT_SEP; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'n' || rest[2] == 'N')) + dotw = TT_SUN; + break; + case 't': case 'T': + if (dotw == TT_UNKNOWN && + (rest[1] == 'h' || rest[1] == 'H') && + (rest[2] == 'u' || rest[2] == 'U')) + dotw = TT_THU; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'e' || rest[2] == 'E')) + dotw = TT_TUE; + break; + case 'u': case 'U': + if (zone == TT_UNKNOWN && + (rest[1] == 't' || rest[1] == 'T') && + !(rest[2] >= 'A' && rest[2] <= 'Z') && + !(rest[2] >= 'a' && rest[2] <= 'z')) + /* UT is the same as GMT but UTx is not. */ + zone = TT_GMT; + break; + case 'w': case 'W': + if (dotw == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'd' || rest[2] == 'D')) + dotw = TT_WED; + break; + + case '+': case '-': + { + const char *end; + int sign; + if (zone_offset != -1) + { + /* already got one... */ + rest++; + break; + } + if (zone != TT_UNKNOWN && zone != TT_GMT) + { + /* GMT+0300 is legal, but PST+0300 is not. */ + rest++; + break; + } + + sign = ((*rest == '+') ? 1 : -1); + rest++; /* move over sign */ + end = rest; + while (*end >= '0' && *end <= '9') + end++; + if (rest == end) /* no digits here */ + break; + + if ((end - rest) == 4) + /* offset in HHMM */ + zone_offset = (((((rest[0]-'0')*10) + (rest[1]-'0')) * 60) + + (((rest[2]-'0')*10) + (rest[3]-'0'))); + else if ((end - rest) == 2) + /* offset in hours */ + zone_offset = (((rest[0]-'0')*10) + (rest[1]-'0')) * 60; + else if ((end - rest) == 1) + /* offset in hours */ + zone_offset = (rest[0]-'0') * 60; + else + /* 3 or >4 */ + break; + + zone_offset *= sign; + zone = TT_GMT; + break; + } + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + int tmp_hour = -1; + int tmp_min = -1; + int tmp_sec = -1; + const char *end = rest + 1; + while (*end >= '0' && *end <= '9') + end++; + + /* end is now the first character after a range of digits. */ + + if (*end == ':') + { + if (hour >= 0 && min >= 0) /* already got it */ + break; + + /* We have seen "[0-9]+:", so this is probably HH:MM[:SS] */ + if ((end - rest) > 2) + /* it is [0-9][0-9][0-9]+: */ + break; + else if ((end - rest) == 2) + tmp_hour = ((rest[0]-'0')*10 + + (rest[1]-'0')); + else + tmp_hour = (rest[0]-'0'); + + /* move over the colon, and parse minutes */ + + rest = ++end; + while (*end >= '0' && *end <= '9') + end++; + + if (end == rest) + /* no digits after first colon? */ + break; + else if ((end - rest) > 2) + /* it is [0-9][0-9][0-9]+: */ + break; + else if ((end - rest) == 2) + tmp_min = ((rest[0]-'0')*10 + + (rest[1]-'0')); + else + tmp_min = (rest[0]-'0'); + + /* now go for seconds */ + rest = end; + if (*rest == ':') + rest++; + end = rest; + while (*end >= '0' && *end <= '9') + end++; + + if (end == rest) + /* no digits after second colon - that's ok. */ + ; + else if ((end - rest) > 2) + /* it is [0-9][0-9][0-9]+: */ + break; + else if ((end - rest) == 2) + tmp_sec = ((rest[0]-'0')*10 + + (rest[1]-'0')); + else + tmp_sec = (rest[0]-'0'); + + /* If we made it here, we've parsed hour and min, + and possibly sec, so it worked as a unit. */ + + /* skip over whitespace and see if there's an AM or PM + directly following the time. + */ + if (tmp_hour <= 12) + { + const char *s = end; + while (*s && (*s == ' ' || *s == '\t')) + s++; + if ((s[0] == 'p' || s[0] == 'P') && + (s[1] == 'm' || s[1] == 'M')) + /* 10:05pm == 22:05, and 12:05pm == 12:05 */ + tmp_hour = (tmp_hour == 12 ? 12 : tmp_hour + 12); + else if (tmp_hour == 12 && + (s[0] == 'a' || s[0] == 'A') && + (s[1] == 'm' || s[1] == 'M')) + /* 12:05am == 00:05 */ + tmp_hour = 0; + } + + hour = tmp_hour; + min = tmp_min; + sec = tmp_sec; + rest = end; + break; + } + else if ((*end == '/' || *end == '-') && + end[1] >= '0' && end[1] <= '9') + { + /* Perhaps this is 6/16/95, 16/6/95, 6-16-95, or 16-6-95 + or even 95-06-05... + #### But it doesn't handle 1995-06-22. + */ + int n1, n2, n3; + const char *s; + + if (month != TT_UNKNOWN) + /* if we saw a month name, this can't be. */ + break; + + s = rest; + + n1 = (*s++ - '0'); /* first 1 or 2 digits */ + if (*s >= '0' && *s <= '9') + n1 = n1*10 + (*s++ - '0'); + + if (*s != '/' && *s != '-') /* slash */ + break; + s++; + + if (*s < '0' || *s > '9') /* second 1 or 2 digits */ + break; + n2 = (*s++ - '0'); + if (*s >= '0' && *s <= '9') + n2 = n2*10 + (*s++ - '0'); + + if (*s != '/' && *s != '-') /* slash */ + break; + s++; + + if (*s < '0' || *s > '9') /* third 1, 2, or 4 digits */ + break; + n3 = (*s++ - '0'); + if (*s >= '0' && *s <= '9') + n3 = n3*10 + (*s++ - '0'); + + if (*s >= '0' && *s <= '9') /* optional digits 3 and 4 */ + { + n3 = n3*10 + (*s++ - '0'); + if (*s < '0' || *s > '9') + break; + n3 = n3*10 + (*s++ - '0'); + } + + if ((*s >= '0' && *s <= '9') || /* followed by non-alphanum */ + (*s >= 'A' && *s <= 'Z') || + (*s >= 'a' && *s <= 'z')) + break; + + /* Ok, we parsed three 1-2 digit numbers, with / or - + between them. Now decide what the hell they are + (DD/MM/YY or MM/DD/YY or YY/MM/DD.) + */ + + if (n1 > 31 || n1 == 0) /* must be YY/MM/DD */ + { + if (n2 > 12) break; + if (n3 > 31) break; + year = n1; + if (year < 70) + year += 2000; + else if (year < 100) + year += 1900; + month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1); + date = n3; + rest = s; + break; + } + + if (n1 > 12 && n2 > 12) /* illegal */ + { + rest = s; + break; + } + + if (n3 < 70) + n3 += 2000; + else if (n3 < 100) + n3 += 1900; + + if (n1 > 12) /* must be DD/MM/YY */ + { + date = n1; + month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1); + year = n3; + } + else /* assume MM/DD/YY */ + { + /* #### In the ambiguous case, should we consult the + locale to find out the local default? */ + month = (TIME_TOKEN)(n1 + ((int)TT_JAN) - 1); + date = n2; + year = n3; + } + rest = s; + } + else if ((*end >= 'A' && *end <= 'Z') || + (*end >= 'a' && *end <= 'z')) + /* Digits followed by non-punctuation - what's that? */ + ; + else if ((end - rest) == 4) /* four digits is a year */ + year = (year < 0 + ? ((rest[0]-'0')*1000L + + (rest[1]-'0')*100L + + (rest[2]-'0')*10L + + (rest[3]-'0')) + : year); + else if ((end - rest) == 2) /* two digits - date or year */ + { + int n = ((rest[0]-'0')*10 + + (rest[1]-'0')); + /* If we don't have a date (day of the month) and we see a number + less than 32, then assume that is the date. + + Otherwise, if we have a date and not a year, assume this is the + year. If it is less than 70, then assume it refers to the 21st + century. If it is two digits (>= 70), assume it refers to this + century. Otherwise, assume it refers to an unambiguous year. + + The world will surely end soon. + */ + if (date < 0 && n < 32) + date = n; + else if (year < 0) + { + if (n < 70) + year = 2000 + n; + else if (n < 100) + year = 1900 + n; + else + year = n; + } + /* else what the hell is this. */ + } + else if ((end - rest) == 1) /* one digit - date */ + date = (date < 0 ? (rest[0]-'0') : date); + /* else, three or more than four digits - what's that? */ + + break; + } + } + + /* Skip to the end of this token, whether we parsed it or not. + Tokens are delimited by whitespace, or ,;-/ + But explicitly not :+-. + */ + while (*rest && + *rest != ' ' && *rest != '\t' && + *rest != ',' && *rest != ';' && + *rest != '-' && *rest != '+' && + *rest != '/' && + *rest != '(' && *rest != ')' && *rest != '[' && *rest != ']') + rest++; + /* skip over uninteresting chars. */ + SKIP_MORE: + while (*rest && + (*rest == ' ' || *rest == '\t' || + *rest == ',' || *rest == ';' || *rest == '/' || + *rest == '(' || *rest == ')' || *rest == '[' || *rest == ']')) + rest++; + + /* "-" is ignored at the beginning of a token if we have not yet + parsed a year (e.g., the second "-" in "30-AUG-1966"), or if + the character after the dash is not a digit. */ + if (*rest == '-' && ((rest > string && isalpha(rest[-1]) && year < 0) + || rest[1] < '0' || rest[1] > '9')) + { + rest++; + goto SKIP_MORE; + } + + } + + if (zone != TT_UNKNOWN && zone_offset == -1) + { + switch (zone) + { + case TT_PST: zone_offset = -8 * 60; break; + case TT_PDT: zone_offset = -7 * 60; break; + case TT_MST: zone_offset = -7 * 60; break; + case TT_MDT: zone_offset = -6 * 60; break; + case TT_CST: zone_offset = -6 * 60; break; + case TT_CDT: zone_offset = -5 * 60; break; + case TT_EST: zone_offset = -5 * 60; break; + case TT_EDT: zone_offset = -4 * 60; break; + case TT_AST: zone_offset = -4 * 60; break; + case TT_NST: zone_offset = -3 * 60 - 30; break; + case TT_GMT: zone_offset = 0 * 60; break; + case TT_BST: zone_offset = 1 * 60; break; + case TT_MET: zone_offset = 1 * 60; break; + case TT_EET: zone_offset = 2 * 60; break; + case TT_JST: zone_offset = 9 * 60; break; + default: + PR_ASSERT (0); + break; + } + } + + /* If we didn't find a year, month, or day-of-the-month, we can't + possibly parse this, and in fact, mktime() will do something random + (I'm seeing it return "Tue Feb 5 06:28:16 2036", which is no doubt + a numerologically significant date... */ + if (month == TT_UNKNOWN || date == -1 || year == -1) + return PR_FAILURE; + + memset(&tm, 0, sizeof(tm)); + if (sec != -1) + tm.tm_sec = sec; + if (min != -1) + tm.tm_min = min; + if (hour != -1) + tm.tm_hour = hour; + if (date != -1) + tm.tm_mday = date; + if (month != TT_UNKNOWN) + tm.tm_month = (((int)month) - ((int)TT_JAN)); + if (year != -1) + tm.tm_year = year; + if (dotw != TT_UNKNOWN) + tm.tm_wday = (((int)dotw) - ((int)TT_SUN)); + + if (zone == TT_UNKNOWN && default_to_gmt) + { + /* No zone was specified, so pretend the zone was GMT. */ + zone = TT_GMT; + zone_offset = 0; + } + + if (zone_offset == -1) + { + /* no zone was specified, and we're to assume that everything + is local. */ + struct tm localTime; + time_t secs; + + PR_ASSERT(tm.tm_month > -1 + && tm.tm_mday > 0 + && tm.tm_hour > -1 + && tm.tm_min > -1 + && tm.tm_sec > -1); + + /* + * To obtain time_t from a tm structure representing the local + * time, we call mktime(). However, we need to see if we are + * on 1-Jan-1970 or before. If we are, we can't call mktime() + * because mktime() will crash on win16. In that case, we + * calculate zone_offset based on the zone offset at + * 00:00:00, 2 Jan 1970 GMT, and subtract zone_offset from the + * date we are parsing to transform the date to GMT. We also + * do so if mktime() returns (time_t) -1 (time out of range). + */ + + /* month, day, hours, mins and secs are always non-negative + so we dont need to worry about them. */ + if(tm.tm_year >= 1970) + { + PRInt64 usec_per_sec; + + localTime.tm_sec = tm.tm_sec; + localTime.tm_min = tm.tm_min; + localTime.tm_hour = tm.tm_hour; + localTime.tm_mday = tm.tm_mday; + localTime.tm_mon = tm.tm_month; + localTime.tm_year = tm.tm_year - 1900; + /* Set this to -1 to tell mktime "I don't care". If you set + it to 0 or 1, you are making assertions about whether the + date you are handing it is in daylight savings mode or not; + and if you're wrong, it will "fix" it for you. */ + localTime.tm_isdst = -1; + secs = mktime(&localTime); + if (secs != (time_t) -1) + { +#if defined(XP_MAC) && (__MSL__ < 0x6000) + /* + * The mktime() routine in MetroWerks MSL C + * Runtime library returns seconds since midnight, + * 1 Jan. 1900, not 1970 - in versions of MSL (Metrowerks Standard + * Library) prior to version 6. Only for older versions of + * MSL do we adjust the value of secs to the NSPR epoch + */ + secs -= ((365 * 70UL) + 17) * 24 * 60 * 60; +#endif + LL_I2L(*result, secs); + LL_I2L(usec_per_sec, PR_USEC_PER_SEC); + LL_MUL(*result, *result, usec_per_sec); + return PR_SUCCESS; + } + } + + /* So mktime() can't handle this case. We assume the + zone_offset for the date we are parsing is the same as + the zone offset on 00:00:00 2 Jan 1970 GMT. */ + secs = 86400; + (void) localtime_s(&localTime, &secs); + zone_offset = localTime.tm_min + + 60 * localTime.tm_hour + + 1440 * (localTime.tm_mday - 2); + } + + tm.tm_params.tp_gmt_offset = zone_offset * 60; + + *result = PR_ImplodeTime(&tm); + + return PR_SUCCESS; +}
\ No newline at end of file diff --git a/base/third_party/nspr/prtime.h b/base/third_party/nspr/prtime.h new file mode 100644 index 0000000..70d25bc --- /dev/null +++ b/base/third_party/nspr/prtime.h @@ -0,0 +1,185 @@ +/* +* Portions are Copyright (C) 2007 Google Inc +* +* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is the Netscape Portable Runtime (NSPR). +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 1998-2000 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** +*/ + +/* + *--------------------------------------------------------------------------- + * + * prtime.h -- + * + * NSPR date and time functions + * CVS revision 3.11 + * This file contains definitions of NSPR's basic types required by + * prtime.cc. These types have been copied over from the following NSPR + * files prtime.h, prtypes.h(CVS revision 3.35), prlong.h(CVS revision 3.13) + * + *--------------------------------------------------------------------------- + */ + +#ifndef BASE_PRTIME_H__ +#define BASE_PRTIME_H__ + +#include "base/logging.h" +#include "base/third_party/nspr/prtypes.h" + +#define PR_ASSERT DCHECK + +#define LL_I2L(l, i) ((l) = (PRInt64)(i)) +#define LL_MUL(r, a, b) ((r) = (a) * (b)) + +/**********************************************************************/ +/************************* TYPES AND CONSTANTS ************************/ +/**********************************************************************/ + +#define PR_MSEC_PER_SEC 1000UL +#define PR_USEC_PER_SEC 1000000UL +#define PR_NSEC_PER_SEC 1000000000UL +#define PR_USEC_PER_MSEC 1000UL +#define PR_NSEC_PER_MSEC 1000000UL + +/* + * PRTime -- + * + * NSPR represents basic time as 64-bit signed integers relative + * to midnight (00:00:00), January 1, 1970 Greenwich Mean Time (GMT). + * (GMT is also known as Coordinated Universal Time, UTC.) + * The units of time are in microseconds. Negative times are allowed + * to represent times prior to the January 1970 epoch. Such values are + * intended to be exported to other systems or converted to human + * readable form. + * + * Notes on porting: PRTime corresponds to time_t in ANSI C. NSPR 1.0 + * simply uses PRInt64. + */ + +typedef PRInt64 PRTime; + +/* + * Time zone and daylight saving time corrections applied to GMT to + * obtain the local time of some geographic location + */ + +typedef struct PRTimeParameters { + PRInt32 tp_gmt_offset; /* the offset from GMT in seconds */ + PRInt32 tp_dst_offset; /* contribution of DST in seconds */ +} PRTimeParameters; + +/* + * PRExplodedTime -- + * + * Time broken down into human-readable components such as year, month, + * day, hour, minute, second, and microsecond. Time zone and daylight + * saving time corrections may be applied. If they are applied, the + * offsets from the GMT must be saved in the 'tm_params' field so that + * all the information is available to reconstruct GMT. + * + * Notes on porting: PRExplodedTime corrresponds to struct tm in + * ANSI C, with the following differences: + * - an additional field tm_usec; + * - replacing tm_isdst by tm_params; + * - the month field is spelled tm_month, not tm_mon; + * - we use absolute year, AD, not the year since 1900. + * The corresponding type in NSPR 1.0 is called PRTime. Below is + * a table of date/time type correspondence in the three APIs: + * API time since epoch time in components + * ANSI C time_t struct tm + * NSPR 1.0 PRInt64 PRTime + * NSPR 2.0 PRTime PRExplodedTime + */ + +typedef struct PRExplodedTime { + PRInt32 tm_usec; /* microseconds past tm_sec (0-99999) */ + PRInt32 tm_sec; /* seconds past tm_min (0-61, accomodating + up to two leap seconds) */ + PRInt32 tm_min; /* minutes past tm_hour (0-59) */ + PRInt32 tm_hour; /* hours past tm_day (0-23) */ + PRInt32 tm_mday; /* days past tm_mon (1-31, note that it + starts from 1) */ + PRInt32 tm_month; /* months past tm_year (0-11, Jan = 0) */ + PRInt16 tm_year; /* absolute year, AD (note that we do not + count from 1900) */ + + PRInt8 tm_wday; /* calculated day of the week + (0-6, Sun = 0) */ + PRInt16 tm_yday; /* calculated day of the year + (0-365, Jan 1 = 0) */ + + PRTimeParameters tm_params; /* time parameters used by conversion */ +} PRExplodedTime; + +NSPR_API(PRTime) +PR_ImplodeTime(const PRExplodedTime *exploded); + +/* + * This parses a time/date string into a PRTime + * (microseconds after "1-Jan-1970 00:00:00 GMT"). + * It returns PR_SUCCESS on success, and PR_FAILURE + * if the time/date string can't be parsed. + * + * Many formats are handled, including: + * + * 14 Apr 89 03:20:12 + * 14 Apr 89 03:20 GMT + * Fri, 17 Mar 89 4:01:33 + * Fri, 17 Mar 89 4:01 GMT + * Mon Jan 16 16:12 PDT 1989 + * Mon Jan 16 16:12 +0130 1989 + * 6 May 1992 16:41-JST (Wednesday) + * 22-AUG-1993 10:59:12.82 + * 22-AUG-1993 10:59pm + * 22-AUG-1993 12:59am + * 22-AUG-1993 12:59 PM + * Friday, August 04, 1995 3:54 PM + * 06/21/95 04:24:34 PM + * 20/06/95 21:07 + * 95-06-08 19:32:48 EDT + * + * If the input string doesn't contain a description of the timezone, + * we consult the `default_to_gmt' to decide whether the string should + * be interpreted relative to the local time zone (PR_FALSE) or GMT (PR_TRUE). + * The correct value for this argument depends on what standard specified + * the time string which you are parsing. + */ + +NSPR_API(PRStatus) PR_ParseTimeString ( + const char *string, + PRBool default_to_gmt, + PRTime *result); + +#endif // BASE_PRTIME_H__ diff --git a/base/third_party/nspr/prtypes.h b/base/third_party/nspr/prtypes.h new file mode 100644 index 0000000..e9e41c2 --- /dev/null +++ b/base/third_party/nspr/prtypes.h @@ -0,0 +1,567 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +** File: prtypes.h +** Description: Definitions of NSPR's basic types +** +** Prototypes and macros used to make up for deficiencies that we have found +** in ANSI environments. +** +** Since we do not wrap <stdlib.h> and all the other standard headers, authors +** of portable code will not know in general that they need these definitions. +** Instead of requiring these authors to find the dependent uses in their code +** and take the following steps only in those C files, we take steps once here +** for all C files. +**/ + +#ifndef prtypes_h___ +#define prtypes_h___ + +#ifdef MDCPUCFG +#include MDCPUCFG +#else +#include "base/third_party/nspr/prcpucfg.h" +#endif + +#include <stddef.h> + +/*********************************************************************** +** MACROS: PR_EXTERN +** PR_IMPLEMENT +** DESCRIPTION: +** These are only for externally visible routines and globals. For +** internal routines, just use "extern" for type checking and that +** will not export internal cross-file or forward-declared symbols. +** Define a macro for declaring procedures return types. We use this to +** deal with windoze specific type hackery for DLL definitions. Use +** PR_EXTERN when the prototype for the method is declared. Use +** PR_IMPLEMENT for the implementation of the method. +** +** Example: +** in dowhim.h +** PR_EXTERN( void ) DoWhatIMean( void ); +** in dowhim.c +** PR_IMPLEMENT( void ) DoWhatIMean( void ) { return; } +** +** +***********************************************************************/ +#if defined(WIN32) + +#define PR_EXPORT(__type) extern __declspec(dllexport) __type +#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPORT(__type) extern __type +#define PR_IMPORT_DATA(__type) __declspec(dllimport) __type + +#define PR_EXTERN(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT(__type) __type +#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(XP_BEOS) + +#define PR_EXPORT(__type) extern __declspec(dllexport) __type +#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPORT(__type) extern __declspec(dllexport) __type +#define PR_IMPORT_DATA(__type) extern __declspec(dllexport) __type + +#define PR_EXTERN(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT(__type) __declspec(dllexport) __type +#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(WIN16) + +#define PR_CALLBACK_DECL __cdecl + +#if defined(_WINDLL) +#define PR_EXPORT(__type) extern __type _cdecl _export _loadds +#define PR_IMPORT(__type) extern __type _cdecl _export _loadds +#define PR_EXPORT_DATA(__type) extern __type _export +#define PR_IMPORT_DATA(__type) extern __type _export + +#define PR_EXTERN(__type) extern __type _cdecl _export _loadds +#define PR_IMPLEMENT(__type) __type _cdecl _export _loadds +#define PR_EXTERN_DATA(__type) extern __type _export +#define PR_IMPLEMENT_DATA(__type) __type _export + +#define PR_CALLBACK __cdecl __loadds +#define PR_STATIC_CALLBACK(__x) static __x PR_CALLBACK + +#else /* this must be .EXE */ +#define PR_EXPORT(__type) extern __type _cdecl _export +#define PR_IMPORT(__type) extern __type _cdecl _export +#define PR_EXPORT_DATA(__type) extern __type _export +#define PR_IMPORT_DATA(__type) extern __type _export + +#define PR_EXTERN(__type) extern __type _cdecl _export +#define PR_IMPLEMENT(__type) __type _cdecl _export +#define PR_EXTERN_DATA(__type) extern __type _export +#define PR_IMPLEMENT_DATA(__type) __type _export + +#define PR_CALLBACK __cdecl __loadds +#define PR_STATIC_CALLBACK(__x) __x PR_CALLBACK +#endif /* _WINDLL */ + +#elif defined(XP_MAC) + +#define PR_EXPORT(__type) extern __declspec(export) __type +#define PR_EXPORT_DATA(__type) extern __declspec(export) __type +#define PR_IMPORT(__type) extern __declspec(export) __type +#define PR_IMPORT_DATA(__type) extern __declspec(export) __type + +#define PR_EXTERN(__type) extern __declspec(export) __type +#define PR_IMPLEMENT(__type) __declspec(export) __type +#define PR_EXTERN_DATA(__type) extern __declspec(export) __type +#define PR_IMPLEMENT_DATA(__type) __declspec(export) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(XP_OS2) && defined(__declspec) + +#define PR_EXPORT(__type) extern __declspec(dllexport) __type +#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPORT(__type) extern __declspec(dllimport) __type +#define PR_IMPORT_DATA(__type) extern __declspec(dllimport) __type + +#define PR_EXTERN(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT(__type) __declspec(dllexport) __type +#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(XP_OS2_VACPP) + +#define PR_EXPORT(__type) extern __type +#define PR_EXPORT_DATA(__type) extern __type +#define PR_IMPORT(__type) extern __type +#define PR_IMPORT_DATA(__type) extern __type + +#define PR_EXTERN(__type) extern __type +#define PR_IMPLEMENT(__type) __type +#define PR_EXTERN_DATA(__type) extern __type +#define PR_IMPLEMENT_DATA(__type) __type +#define PR_CALLBACK _Optlink +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x PR_CALLBACK + +#else /* Unix */ + +/* GCC 3.3 and later support the visibility attribute. */ +#if (__GNUC__ >= 4) || \ + (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) +#define PR_VISIBILITY_DEFAULT __attribute__((visibility("default"))) +#else +#define PR_VISIBILITY_DEFAULT +#endif + +#define PR_EXPORT(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_EXPORT_DATA(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPORT(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPORT_DATA(__type) extern PR_VISIBILITY_DEFAULT __type + +#define PR_EXTERN(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPLEMENT(__type) PR_VISIBILITY_DEFAULT __type +#define PR_EXTERN_DATA(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPLEMENT_DATA(__type) PR_VISIBILITY_DEFAULT __type +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#endif + +#if defined(_NSPR_BUILD_) +#define NSPR_API(__type) PR_EXPORT(__type) +#define NSPR_DATA_API(__type) PR_EXPORT_DATA(__type) +#else +#define NSPR_API(__type) PR_IMPORT(__type) +#define NSPR_DATA_API(__type) PR_IMPORT_DATA(__type) +#endif + +/*********************************************************************** +** MACROS: PR_BEGIN_MACRO +** PR_END_MACRO +** DESCRIPTION: +** Macro body brackets so that macros with compound statement definitions +** behave syntactically more like functions when called. +***********************************************************************/ +#define PR_BEGIN_MACRO do { +#define PR_END_MACRO } while (0) + +/*********************************************************************** +** MACROS: PR_BEGIN_EXTERN_C +** PR_END_EXTERN_C +** DESCRIPTION: +** Macro shorthands for conditional C++ extern block delimiters. +***********************************************************************/ +#ifdef __cplusplus +#define PR_BEGIN_EXTERN_C extern "C" { +#define PR_END_EXTERN_C } +#else +#define PR_BEGIN_EXTERN_C +#define PR_END_EXTERN_C +#endif + +/*********************************************************************** +** MACROS: PR_BIT +** PR_BITMASK +** DESCRIPTION: +** Bit masking macros. XXX n must be <= 31 to be portable +***********************************************************************/ +#define PR_BIT(n) ((PRUint32)1 << (n)) +#define PR_BITMASK(n) (PR_BIT(n) - 1) + +/*********************************************************************** +** MACROS: PR_ROUNDUP +** PR_MIN +** PR_MAX +** PR_ABS +** DESCRIPTION: +** Commonly used macros for operations on compatible types. +***********************************************************************/ +#define PR_ROUNDUP(x,y) ((((x)+((y)-1))/(y))*(y)) +#define PR_MIN(x,y) ((x)<(y)?(x):(y)) +#define PR_MAX(x,y) ((x)>(y)?(x):(y)) +#define PR_ABS(x) ((x)<0?-(x):(x)) + +PR_BEGIN_EXTERN_C + +/************************************************************************ +** TYPES: PRUint8 +** PRInt8 +** DESCRIPTION: +** The int8 types are known to be 8 bits each. There is no type that +** is equivalent to a plain "char". +************************************************************************/ +#if PR_BYTES_PER_BYTE == 1 +typedef unsigned char PRUint8; +/* +** Some cfront-based C++ compilers do not like 'signed char' and +** issue the warning message: +** warning: "signed" not implemented (ignored) +** For these compilers, we have to define PRInt8 as plain 'char'. +** Make sure that plain 'char' is indeed signed under these compilers. +*/ +#if (defined(HPUX) && defined(__cplusplus) \ + && !defined(__GNUC__) && __cplusplus < 199707L) \ + || (defined(SCO) && defined(__cplusplus) \ + && !defined(__GNUC__) && __cplusplus == 1L) +typedef char PRInt8; +#else +typedef signed char PRInt8; +#endif +#else +#error No suitable type for PRInt8/PRUint8 +#endif + +/************************************************************************ + * MACROS: PR_INT8_MAX + * PR_INT8_MIN + * PR_UINT8_MAX + * DESCRIPTION: + * The maximum and minimum values of a PRInt8 or PRUint8. +************************************************************************/ + +#define PR_INT8_MAX 127 +#define PR_INT8_MIN (-128) +#define PR_UINT8_MAX 255U + +/************************************************************************ +** TYPES: PRUint16 +** PRInt16 +** DESCRIPTION: +** The int16 types are known to be 16 bits each. +************************************************************************/ +#if PR_BYTES_PER_SHORT == 2 +typedef unsigned short PRUint16; +typedef short PRInt16; +#else +#error No suitable type for PRInt16/PRUint16 +#endif + +/************************************************************************ + * MACROS: PR_INT16_MAX + * PR_INT16_MIN + * PR_UINT16_MAX + * DESCRIPTION: + * The maximum and minimum values of a PRInt16 or PRUint16. +************************************************************************/ + +#define PR_INT16_MAX 32767 +#define PR_INT16_MIN (-32768) +#define PR_UINT16_MAX 65535U + +/************************************************************************ +** TYPES: PRUint32 +** PRInt32 +** DESCRIPTION: +** The int32 types are known to be 32 bits each. +************************************************************************/ +#if PR_BYTES_PER_INT == 4 +typedef unsigned int PRUint32; +typedef int PRInt32; +#define PR_INT32(x) x +#define PR_UINT32(x) x ## U +#elif PR_BYTES_PER_LONG == 4 +typedef unsigned long PRUint32; +typedef long PRInt32; +#define PR_INT32(x) x ## L +#define PR_UINT32(x) x ## UL +#else +#error No suitable type for PRInt32/PRUint32 +#endif + +/************************************************************************ + * MACROS: PR_INT32_MAX + * PR_INT32_MIN + * PR_UINT32_MAX + * DESCRIPTION: + * The maximum and minimum values of a PRInt32 or PRUint32. +************************************************************************/ + +#define PR_INT32_MAX PR_INT32(2147483647) +#define PR_INT32_MIN (-PR_INT32_MAX - 1) +#define PR_UINT32_MAX PR_UINT32(4294967295) + +/************************************************************************ +** TYPES: PRUint64 +** PRInt64 +** DESCRIPTION: +** The int64 types are known to be 64 bits each. Care must be used when +** declaring variables of type PRUint64 or PRInt64. Different hardware +** architectures and even different compilers have varying support for +** 64 bit values. The only guaranteed portability requires the use of +** the LL_ macros (see prlong.h). +************************************************************************/ +#ifdef HAVE_LONG_LONG +#if PR_BYTES_PER_LONG == 8 +typedef long PRInt64; +typedef unsigned long PRUint64; +#elif defined(WIN16) +typedef __int64 PRInt64; +typedef unsigned __int64 PRUint64; +#elif defined(WIN32) && !defined(__GNUC__) +typedef __int64 PRInt64; +typedef unsigned __int64 PRUint64; +#else +typedef long long PRInt64; +typedef unsigned long long PRUint64; +#endif /* PR_BYTES_PER_LONG == 8 */ +#else /* !HAVE_LONG_LONG */ +typedef struct { +#ifdef IS_LITTLE_ENDIAN + PRUint32 lo, hi; +#else + PRUint32 hi, lo; +#endif +} PRInt64; +typedef PRInt64 PRUint64; +#endif /* !HAVE_LONG_LONG */ + +/************************************************************************ +** TYPES: PRUintn +** PRIntn +** DESCRIPTION: +** The PRIntn types are most appropriate for automatic variables. They are +** guaranteed to be at least 16 bits, though various architectures may +** define them to be wider (e.g., 32 or even 64 bits). These types are +** never valid for fields of a structure. +************************************************************************/ +#if PR_BYTES_PER_INT >= 2 +typedef int PRIntn; +typedef unsigned int PRUintn; +#else +#error 'sizeof(int)' not sufficient for platform use +#endif + +/************************************************************************ +** TYPES: PRFloat64 +** DESCRIPTION: +** NSPR's floating point type is always 64 bits. +************************************************************************/ +typedef double PRFloat64; + +/************************************************************************ +** TYPES: PRSize +** DESCRIPTION: +** A type for representing the size of objects. +************************************************************************/ +typedef size_t PRSize; + + +/************************************************************************ +** TYPES: PROffset32, PROffset64 +** DESCRIPTION: +** A type for representing byte offsets from some location. +************************************************************************/ +typedef PRInt32 PROffset32; +typedef PRInt64 PROffset64; + +/************************************************************************ +** TYPES: PRPtrDiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer subtraction. +************************************************************************/ +typedef ptrdiff_t PRPtrdiff; + +/************************************************************************ +** TYPES: PRUptrdiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer sutraction. +************************************************************************/ +#ifdef _WIN64 +typedef unsigned __int64 PRUptrdiff; +#else +typedef unsigned long PRUptrdiff; +#endif + +/************************************************************************ +** TYPES: PRBool +** DESCRIPTION: +** Use PRBool for variables and parameter types. Use PR_FALSE and PR_TRUE +** for clarity of target type in assignments and actual arguments. Use +** 'if (bool)', 'while (!bool)', '(bool) ? x : y' etc., to test booleans +** just as you would C int-valued conditions. +************************************************************************/ +typedef PRIntn PRBool; +#define PR_TRUE 1 +#define PR_FALSE 0 + +/************************************************************************ +** TYPES: PRPackedBool +** DESCRIPTION: +** Use PRPackedBool within structs where bitfields are not desirable +** but minimum and consistant overhead matters. +************************************************************************/ +typedef PRUint8 PRPackedBool; + +/* +** Status code used by some routines that have a single point of failure or +** special status return. +*/ +typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus; + +#ifndef __PRUNICHAR__ +#define __PRUNICHAR__ +#if defined(WIN32) || defined(XP_MAC) +typedef wchar_t PRUnichar; +#else +typedef PRUint16 PRUnichar; +#endif +#endif + +/* +** WARNING: The undocumented data types PRWord and PRUword are +** only used in the garbage collection and arena code. Do not +** use PRWord and PRUword in new code. +** +** A PRWord is an integer that is the same size as a void*. +** It implements the notion of a "word" in the Java Virtual +** Machine. (See Sec. 3.4 "Words", The Java Virtual Machine +** Specification, Addison-Wesley, September 1996. +** http://java.sun.com/docs/books/vmspec/index.html.) +*/ +#ifdef _WIN64 +typedef __int64 PRWord; +typedef unsigned __int64 PRUword; +#else +typedef long PRWord; +typedef unsigned long PRUword; +#endif + +#if defined(NO_NSPR_10_SUPPORT) +#else +/********* ???????????????? FIX ME ??????????????????????????? *****/ +/********************** Some old definitions until pr=>ds transition is done ***/ +/********************** Also, we are still using NSPR 1.0. GC ******************/ +/* +** Fundamental NSPR macros, used nearly everywhere. +*/ + +#define PR_PUBLIC_API PR_IMPLEMENT + +/* +** Macro body brackets so that macros with compound statement definitions +** behave syntactically more like functions when called. +*/ +#define NSPR_BEGIN_MACRO do { +#define NSPR_END_MACRO } while (0) + +/* +** Macro shorthands for conditional C++ extern block delimiters. +*/ +#ifdef NSPR_BEGIN_EXTERN_C +#undef NSPR_BEGIN_EXTERN_C +#endif +#ifdef NSPR_END_EXTERN_C +#undef NSPR_END_EXTERN_C +#endif + +#ifdef __cplusplus +#define NSPR_BEGIN_EXTERN_C extern "C" { +#define NSPR_END_EXTERN_C } +#else +#define NSPR_BEGIN_EXTERN_C +#define NSPR_END_EXTERN_C +#endif + +/********* ????????????? End Fix me ?????????????????????????????? *****/ +#endif /* NO_NSPR_10_SUPPORT */ + +PR_END_EXTERN_C + +#if !defined(NO_NSPR_10_SUPPORT) +#include "base/basictypes.h" +#endif + +#endif /* prtypes_h___ */ + diff --git a/base/third_party/nss/README.google b/base/third_party/nss/README.google new file mode 100644 index 0000000..7106351 --- /dev/null +++ b/base/third_party/nss/README.google @@ -0,0 +1,8 @@ +The original code is the Network Security Services (NSS), licensed under +the MPL/GPL/LGPL tri-license (http://www.mozilla.org/MPL/). + +We extracted the SHA-256 source files, eliminated unneeded dependencies, +deleted or commented out unused code, and tweaked them for Chrome's source +tree. sha512.c is renamed sha512.cc so that it can include Chrome's C++ +header "base/basictypes.h". We define NOUNROLL256 to reduce the object code +size. diff --git a/base/third_party/nss/blapi.h b/base/third_party/nss/blapi.h new file mode 100644 index 0000000..6e57ee0 --- /dev/null +++ b/base/third_party/nss/blapi.h @@ -0,0 +1,101 @@ +/* + * crypto.h - public data structures and prototypes for the crypto library + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1994-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dr Vipul Gupta <vipul.gupta@sun.com>, Sun Microsystems Laboratories + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +/* $Id: blapi.h,v 1.27 2007/11/09 18:49:32 wtc%google.com Exp $ */ + +#ifndef _BLAPI_H_ +#define _BLAPI_H_ + +#include "base/third_party/nss/blapit.h" + +/******************************************/ + +extern SHA256Context *SHA256_NewContext(void); +extern void SHA256_DestroyContext(SHA256Context *cx, PRBool freeit); +extern void SHA256_Begin(SHA256Context *cx); +extern void SHA256_Update(SHA256Context *cx, const unsigned char *input, + unsigned int inputLen); +extern void SHA256_End(SHA256Context *cx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen); +extern SECStatus SHA256_HashBuf(unsigned char *dest, const unsigned char *src, + uint32 src_length); +extern SECStatus SHA256_Hash(unsigned char *dest, const char *src); +extern void SHA256_TraceState(SHA256Context *cx); +extern unsigned int SHA256_FlattenSize(SHA256Context *cx); +extern SECStatus SHA256_Flatten(SHA256Context *cx,unsigned char *space); +extern SHA256Context * SHA256_Resurrect(unsigned char *space, void *arg); +extern void SHA256_Clone(SHA256Context *dest, SHA256Context *src); + +/******************************************/ + +extern SHA512Context *SHA512_NewContext(void); +extern void SHA512_DestroyContext(SHA512Context *cx, PRBool freeit); +extern void SHA512_Begin(SHA512Context *cx); +extern void SHA512_Update(SHA512Context *cx, const unsigned char *input, + unsigned int inputLen); +extern void SHA512_End(SHA512Context *cx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen); +extern SECStatus SHA512_HashBuf(unsigned char *dest, const unsigned char *src, + uint32 src_length); +extern SECStatus SHA512_Hash(unsigned char *dest, const char *src); +extern void SHA512_TraceState(SHA512Context *cx); +extern unsigned int SHA512_FlattenSize(SHA512Context *cx); +extern SECStatus SHA512_Flatten(SHA512Context *cx,unsigned char *space); +extern SHA512Context * SHA512_Resurrect(unsigned char *space, void *arg); +extern void SHA512_Clone(SHA512Context *dest, SHA512Context *src); + +/******************************************/ + +extern SHA384Context *SHA384_NewContext(void); +extern void SHA384_DestroyContext(SHA384Context *cx, PRBool freeit); +extern void SHA384_Begin(SHA384Context *cx); +extern void SHA384_Update(SHA384Context *cx, const unsigned char *input, + unsigned int inputLen); +extern void SHA384_End(SHA384Context *cx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen); +extern SECStatus SHA384_HashBuf(unsigned char *dest, const unsigned char *src, + uint32 src_length); +extern SECStatus SHA384_Hash(unsigned char *dest, const char *src); +extern void SHA384_TraceState(SHA384Context *cx); +extern unsigned int SHA384_FlattenSize(SHA384Context *cx); +extern SECStatus SHA384_Flatten(SHA384Context *cx,unsigned char *space); +extern SHA384Context * SHA384_Resurrect(unsigned char *space, void *arg); +extern void SHA384_Clone(SHA384Context *dest, SHA384Context *src); + +#endif /* _BLAPI_H_ */ diff --git a/base/third_party/nss/blapit.h b/base/third_party/nss/blapit.h new file mode 100644 index 0000000..e16a084 --- /dev/null +++ b/base/third_party/nss/blapit.h @@ -0,0 +1,91 @@ +/* + * blapit.h - public data structures for the crypto library + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1994-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dr Vipul Gupta <vipul.gupta@sun.com> and + * Douglas Stebila <douglas@stebila.ca>, Sun Microsystems Laboratories + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +/* $Id: blapit.h,v 1.20 2007/02/28 19:47:37 rrelyea%redhat.com Exp $ */ + +#ifndef _BLAPIT_H_ +#define _BLAPIT_H_ + +#include "base/third_party/nspr/prtypes.h" + +/* +** A status code. Status's are used by procedures that return status +** values. Again the motivation is so that a compiler can generate +** warnings when return values are wrong. Correct testing of status codes: +** +** SECStatus rv; +** rv = some_function (some_argument); +** if (rv != SECSuccess) +** do_an_error_thing(); +** +*/ +typedef enum _SECStatus { + SECWouldBlock = -2, + SECFailure = -1, + SECSuccess = 0 +} SECStatus; + +#define SHA256_LENGTH 32 /* bytes */ +#define SHA384_LENGTH 48 /* bytes */ +#define SHA512_LENGTH 64 /* bytes */ +#define HASH_LENGTH_MAX SHA512_LENGTH + +/* + * Input block size for each hash algorithm. + */ + +#define SHA256_BLOCK_LENGTH 64 /* bytes */ +#define SHA384_BLOCK_LENGTH 128 /* bytes */ +#define SHA512_BLOCK_LENGTH 128 /* bytes */ +#define HASH_BLOCK_LENGTH_MAX SHA512_BLOCK_LENGTH + +/*************************************************************************** +** Opaque objects +*/ + +struct SHA256ContextStr ; +struct SHA512ContextStr ; + +typedef struct SHA256ContextStr SHA256Context; +typedef struct SHA512ContextStr SHA512Context; +/* SHA384Context is really a SHA512ContextStr. This is not a mistake. */ +typedef struct SHA512ContextStr SHA384Context; + +#endif /* _BLAPIT_H_ */ diff --git a/base/third_party/nss/sha256.h b/base/third_party/nss/sha256.h new file mode 100644 index 0000000..e641b49 --- /dev/null +++ b/base/third_party/nss/sha256.h @@ -0,0 +1,51 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _SHA_256_H_ +#define _SHA_256_H_ + +#include "base/third_party/nspr/prtypes.h" + +struct SHA256ContextStr { + union { + PRUint32 w[64]; /* message schedule, input buffer, plus 48 words */ + PRUint8 b[256]; + } u; + PRUint32 h[8]; /* 8 state variables */ + PRUint32 sizeHi,sizeLo; /* 64-bit count of hashed bytes. */ +}; + +#endif /* _SHA_256_H_ */ diff --git a/base/third_party/nss/sha512.cc b/base/third_party/nss/sha512.cc new file mode 100644 index 0000000..d17f532c --- /dev/null +++ b/base/third_party/nss/sha512.cc @@ -0,0 +1,1396 @@ +/* + * sha512.c - implementation of SHA256, SHA384 and SHA512 + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +/* $Id: sha512.c,v 1.9 2006/10/13 16:54:04 wtchang%redhat.com Exp $ */ + +// Prevent manual unrolling in the sha256 code, which reduces the binary code +// size from ~10k to ~1k. The performance should be reasonable for our use. +#define NOUNROLL256 1 + +#if defined(_M_IX86) || defined(i386) || defined(__i386) || defined(__i386__) +#define _X86_ 1 +#endif + +#include "base/third_party/nspr/prcpucfg.h" +#if defined(_X86_) || defined(SHA_NO_LONG_LONG) +#define NOUNROLL512 1 +#undef HAVE_LONG_LONG +#endif +#include "base/third_party/nspr/prtypes.h" /* for PRUintXX */ +#include "base/third_party/nss/blapi.h" +#include "base/third_party/nss/sha256.h" /* for struct SHA256ContextStr */ + +#include <stdlib.h> +#include <string.h> +#define PORT_New(type) static_cast<type*>(malloc(sizeof(type))) +#define PORT_ZFree(ptr, len) do { memset(ptr, 0, len); free(ptr); } while (0) +#define PORT_Strlen(s) static_cast<uint32>(strlen(s)) +#define PORT_Memcpy memcpy + +/* ============= Common constants and defines ======================= */ + +#define W ctx->u.w +#define B ctx->u.b +#define H ctx->h + +#define SHR(x,n) (x >> n) +#define SHL(x,n) (x << n) +#define Ch(x,y,z) ((x & y) ^ (~x & z)) +#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) + +/* Padding used with all flavors of SHA */ +static const PRUint8 pad[240] = { +0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + /* compiler will fill the rest in with zeros */ +}; + +/* ============= SHA256 implemenmtation ================================== */ + +/* SHA-256 constants, K256. */ +static const PRUint32 K256[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/* SHA-256 initial hash values */ +static const PRUint32 H256[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +#if defined(_MSC_VER) && defined(_X86_) +#ifndef FORCEINLINE +#if (_MSC_VER >= 1200) +#define FORCEINLINE __forceinline +#else +#define FORCEINLINE __inline +#endif +#endif +#define FASTCALL __fastcall + +static FORCEINLINE PRUint32 FASTCALL +swap4b(PRUint32 dwd) +{ + __asm { + mov eax,dwd + bswap eax + } +} + +#define SHA_HTONL(x) swap4b(x) +#define BYTESWAP4(x) x = SHA_HTONL(x) + +#elif defined(LINUX) && defined(_X86_) +#undef __OPTIMIZE__ +#define __OPTIMIZE__ 1 +#undef __pentium__ +#define __pentium__ 1 +#include <byteswap.h> +#define SHA_HTONL(x) bswap_32(x) +#define BYTESWAP4(x) x = SHA_HTONL(x) + +#else /* neither windows nor Linux PC */ +#define SWAP4MASK 0x00FF00FF +#define SHA_HTONL(x) (t1 = (x), t1 = (t1 << 16) | (t1 >> 16), \ + ((t1 & SWAP4MASK) << 8) | ((t1 >> 8) & SWAP4MASK)) +#define BYTESWAP4(x) x = SHA_HTONL(x) +#endif + +#if defined(_MSC_VER) && defined(_X86_) +#pragma intrinsic (_lrotr, _lrotl) +#define ROTR32(x,n) _lrotr(x,n) +#define ROTL32(x,n) _lrotl(x,n) +#else +#define ROTR32(x,n) ((x >> n) | (x << ((8 * sizeof x) - n))) +#define ROTL32(x,n) ((x << n) | (x >> ((8 * sizeof x) - n))) +#endif + +/* Capitol Sigma and lower case sigma functions */ +#define S0(x) (ROTR32(x, 2) ^ ROTR32(x,13) ^ ROTR32(x,22)) +#define S1(x) (ROTR32(x, 6) ^ ROTR32(x,11) ^ ROTR32(x,25)) +#define s0(x) (t1 = x, ROTR32(t1, 7) ^ ROTR32(t1,18) ^ SHR(t1, 3)) +#define s1(x) (t2 = x, ROTR32(t2,17) ^ ROTR32(t2,19) ^ SHR(t2,10)) + +SHA256Context * +SHA256_NewContext(void) +{ + SHA256Context *ctx = PORT_New(SHA256Context); + return ctx; +} + +void +SHA256_DestroyContext(SHA256Context *ctx, PRBool freeit) +{ + if (freeit) { + PORT_ZFree(ctx, sizeof *ctx); + } +} + +void +SHA256_Begin(SHA256Context *ctx) +{ + memset(ctx, 0, sizeof *ctx); + memcpy(H, H256, sizeof H256); +} + +static void +SHA256_Compress(SHA256Context *ctx) +{ + { + register PRUint32 t1, t2; + +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP4(W[0]); + BYTESWAP4(W[1]); + BYTESWAP4(W[2]); + BYTESWAP4(W[3]); + BYTESWAP4(W[4]); + BYTESWAP4(W[5]); + BYTESWAP4(W[6]); + BYTESWAP4(W[7]); + BYTESWAP4(W[8]); + BYTESWAP4(W[9]); + BYTESWAP4(W[10]); + BYTESWAP4(W[11]); + BYTESWAP4(W[12]); + BYTESWAP4(W[13]); + BYTESWAP4(W[14]); + BYTESWAP4(W[15]); +#endif + +#define INITW(t) W[t] = (s1(W[t-2]) + W[t-7] + s0(W[t-15]) + W[t-16]) + + /* prepare the "message schedule" */ +#ifdef NOUNROLL256 + { + int t; + for (t = 16; t < 64; ++t) { + INITW(t); + } + } +#else + INITW(16); + INITW(17); + INITW(18); + INITW(19); + + INITW(20); + INITW(21); + INITW(22); + INITW(23); + INITW(24); + INITW(25); + INITW(26); + INITW(27); + INITW(28); + INITW(29); + + INITW(30); + INITW(31); + INITW(32); + INITW(33); + INITW(34); + INITW(35); + INITW(36); + INITW(37); + INITW(38); + INITW(39); + + INITW(40); + INITW(41); + INITW(42); + INITW(43); + INITW(44); + INITW(45); + INITW(46); + INITW(47); + INITW(48); + INITW(49); + + INITW(50); + INITW(51); + INITW(52); + INITW(53); + INITW(54); + INITW(55); + INITW(56); + INITW(57); + INITW(58); + INITW(59); + + INITW(60); + INITW(61); + INITW(62); + INITW(63); + +#endif +#undef INITW + } + { + PRUint32 a, b, c, d, e, f, g, h; + + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + +#define ROUND(n,a,b,c,d,e,f,g,h) \ + h += S1(e) + Ch(e,f,g) + K256[n] + W[n]; \ + d += h; \ + h += S0(a) + Maj(a,b,c); + +#ifdef NOUNROLL256 + { + int t; + for (t = 0; t < 64; t+= 8) { + ROUND(t+0,a,b,c,d,e,f,g,h) + ROUND(t+1,h,a,b,c,d,e,f,g) + ROUND(t+2,g,h,a,b,c,d,e,f) + ROUND(t+3,f,g,h,a,b,c,d,e) + ROUND(t+4,e,f,g,h,a,b,c,d) + ROUND(t+5,d,e,f,g,h,a,b,c) + ROUND(t+6,c,d,e,f,g,h,a,b) + ROUND(t+7,b,c,d,e,f,g,h,a) + } + } +#else + ROUND( 0,a,b,c,d,e,f,g,h) + ROUND( 1,h,a,b,c,d,e,f,g) + ROUND( 2,g,h,a,b,c,d,e,f) + ROUND( 3,f,g,h,a,b,c,d,e) + ROUND( 4,e,f,g,h,a,b,c,d) + ROUND( 5,d,e,f,g,h,a,b,c) + ROUND( 6,c,d,e,f,g,h,a,b) + ROUND( 7,b,c,d,e,f,g,h,a) + + ROUND( 8,a,b,c,d,e,f,g,h) + ROUND( 9,h,a,b,c,d,e,f,g) + ROUND(10,g,h,a,b,c,d,e,f) + ROUND(11,f,g,h,a,b,c,d,e) + ROUND(12,e,f,g,h,a,b,c,d) + ROUND(13,d,e,f,g,h,a,b,c) + ROUND(14,c,d,e,f,g,h,a,b) + ROUND(15,b,c,d,e,f,g,h,a) + + ROUND(16,a,b,c,d,e,f,g,h) + ROUND(17,h,a,b,c,d,e,f,g) + ROUND(18,g,h,a,b,c,d,e,f) + ROUND(19,f,g,h,a,b,c,d,e) + ROUND(20,e,f,g,h,a,b,c,d) + ROUND(21,d,e,f,g,h,a,b,c) + ROUND(22,c,d,e,f,g,h,a,b) + ROUND(23,b,c,d,e,f,g,h,a) + + ROUND(24,a,b,c,d,e,f,g,h) + ROUND(25,h,a,b,c,d,e,f,g) + ROUND(26,g,h,a,b,c,d,e,f) + ROUND(27,f,g,h,a,b,c,d,e) + ROUND(28,e,f,g,h,a,b,c,d) + ROUND(29,d,e,f,g,h,a,b,c) + ROUND(30,c,d,e,f,g,h,a,b) + ROUND(31,b,c,d,e,f,g,h,a) + + ROUND(32,a,b,c,d,e,f,g,h) + ROUND(33,h,a,b,c,d,e,f,g) + ROUND(34,g,h,a,b,c,d,e,f) + ROUND(35,f,g,h,a,b,c,d,e) + ROUND(36,e,f,g,h,a,b,c,d) + ROUND(37,d,e,f,g,h,a,b,c) + ROUND(38,c,d,e,f,g,h,a,b) + ROUND(39,b,c,d,e,f,g,h,a) + + ROUND(40,a,b,c,d,e,f,g,h) + ROUND(41,h,a,b,c,d,e,f,g) + ROUND(42,g,h,a,b,c,d,e,f) + ROUND(43,f,g,h,a,b,c,d,e) + ROUND(44,e,f,g,h,a,b,c,d) + ROUND(45,d,e,f,g,h,a,b,c) + ROUND(46,c,d,e,f,g,h,a,b) + ROUND(47,b,c,d,e,f,g,h,a) + + ROUND(48,a,b,c,d,e,f,g,h) + ROUND(49,h,a,b,c,d,e,f,g) + ROUND(50,g,h,a,b,c,d,e,f) + ROUND(51,f,g,h,a,b,c,d,e) + ROUND(52,e,f,g,h,a,b,c,d) + ROUND(53,d,e,f,g,h,a,b,c) + ROUND(54,c,d,e,f,g,h,a,b) + ROUND(55,b,c,d,e,f,g,h,a) + + ROUND(56,a,b,c,d,e,f,g,h) + ROUND(57,h,a,b,c,d,e,f,g) + ROUND(58,g,h,a,b,c,d,e,f) + ROUND(59,f,g,h,a,b,c,d,e) + ROUND(60,e,f,g,h,a,b,c,d) + ROUND(61,d,e,f,g,h,a,b,c) + ROUND(62,c,d,e,f,g,h,a,b) + ROUND(63,b,c,d,e,f,g,h,a) +#endif + + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; + } +#undef ROUND +} + +#undef s0 +#undef s1 +#undef S0 +#undef S1 + +void +SHA256_Update(SHA256Context *ctx, const unsigned char *input, + unsigned int inputLen) +{ + unsigned int inBuf = ctx->sizeLo & 0x3f; + if (!inputLen) + return; + + /* Add inputLen into the count of bytes processed, before processing */ + if ((ctx->sizeLo += inputLen) < inputLen) + ctx->sizeHi++; + + /* if data already in buffer, attemp to fill rest of buffer */ + if (inBuf) { + unsigned int todo = SHA256_BLOCK_LENGTH - inBuf; + if (inputLen < todo) + todo = inputLen; + memcpy(B + inBuf, input, todo); + input += todo; + inputLen -= todo; + if (inBuf + todo == SHA256_BLOCK_LENGTH) + SHA256_Compress(ctx); + } + + /* if enough data to fill one or more whole buffers, process them. */ + while (inputLen >= SHA256_BLOCK_LENGTH) { + memcpy(B, input, SHA256_BLOCK_LENGTH); + input += SHA256_BLOCK_LENGTH; + inputLen -= SHA256_BLOCK_LENGTH; + SHA256_Compress(ctx); + } + /* if data left over, fill it into buffer */ + if (inputLen) + memcpy(B, input, inputLen); +} + +void +SHA256_End(SHA256Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ + unsigned int inBuf = ctx->sizeLo & 0x3f; + unsigned int padLen = (inBuf < 56) ? (56 - inBuf) : (56 + 64 - inBuf); + PRUint32 hi, lo; +#ifdef SWAP4MASK + PRUint32 t1; +#endif + + hi = (ctx->sizeHi << 3) | (ctx->sizeLo >> 29); + lo = (ctx->sizeLo << 3); + + SHA256_Update(ctx, pad, padLen); + +#if defined(IS_LITTLE_ENDIAN) + W[14] = SHA_HTONL(hi); + W[15] = SHA_HTONL(lo); +#else + W[14] = hi; + W[15] = lo; +#endif + SHA256_Compress(ctx); + + /* now output the answer */ +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP4(H[0]); + BYTESWAP4(H[1]); + BYTESWAP4(H[2]); + BYTESWAP4(H[3]); + BYTESWAP4(H[4]); + BYTESWAP4(H[5]); + BYTESWAP4(H[6]); + BYTESWAP4(H[7]); +#endif + padLen = PR_MIN(SHA256_LENGTH, maxDigestLen); + memcpy(digest, H, padLen); + if (digestLen) + *digestLen = padLen; +} + +/* Comment out unused code, mostly the SHA384 and SHA512 implementations. */ +#if 0 +SECStatus +SHA256_HashBuf(unsigned char *dest, const unsigned char *src, + uint32 src_length) +{ + SHA256Context ctx; + unsigned int outLen; + + SHA256_Begin(&ctx); + SHA256_Update(&ctx, src, src_length); + SHA256_End(&ctx, dest, &outLen, SHA256_LENGTH); + + return SECSuccess; +} + + +SECStatus +SHA256_Hash(unsigned char *dest, const char *src) +{ + return SHA256_HashBuf(dest, (const unsigned char *)src, PORT_Strlen(src)); +} + + +void SHA256_TraceState(SHA256Context *ctx) { } + +unsigned int +SHA256_FlattenSize(SHA256Context *ctx) +{ + return sizeof *ctx; +} + +SECStatus +SHA256_Flatten(SHA256Context *ctx,unsigned char *space) +{ + PORT_Memcpy(space, ctx, sizeof *ctx); + return SECSuccess; +} + +SHA256Context * +SHA256_Resurrect(unsigned char *space, void *arg) +{ + SHA256Context *ctx = SHA256_NewContext(); + if (ctx) + PORT_Memcpy(ctx, space, sizeof *ctx); + return ctx; +} + +void SHA256_Clone(SHA256Context *dest, SHA256Context *src) +{ + memcpy(dest, src, sizeof *dest); +} + + +/* ======= SHA512 and SHA384 common constants and defines ================= */ + +/* common #defines for SHA512 and SHA384 */ +#if defined(HAVE_LONG_LONG) +#define ROTR64(x,n) ((x >> n) | (x << (64 - n))) +#define ROTL64(x,n) ((x << n) | (x >> (64 - n))) + +#define S0(x) (ROTR64(x,28) ^ ROTR64(x,34) ^ ROTR64(x,39)) +#define S1(x) (ROTR64(x,14) ^ ROTR64(x,18) ^ ROTR64(x,41)) +#define s0(x) (t1 = x, ROTR64(t1, 1) ^ ROTR64(t1, 8) ^ SHR(t1,7)) +#define s1(x) (t2 = x, ROTR64(t2,19) ^ ROTR64(t2,61) ^ SHR(t2,6)) + +#if PR_BYTES_PER_LONG == 8 +#define ULLC(hi,lo) 0x ## hi ## lo ## UL +#elif defined(_MSC_VER) +#define ULLC(hi,lo) 0x ## hi ## lo ## ui64 +#else +#define ULLC(hi,lo) 0x ## hi ## lo ## ULL +#endif + +#define SHA_MASK16 ULLC(0000FFFF,0000FFFF) +#define SHA_MASK8 ULLC(00FF00FF,00FF00FF) +#define SHA_HTONLL(x) (t1 = x, \ + t1 = ((t1 & SHA_MASK8 ) << 8) | ((t1 >> 8) & SHA_MASK8 ), \ + t1 = ((t1 & SHA_MASK16) << 16) | ((t1 >> 16) & SHA_MASK16), \ + (t1 >> 32) | (t1 << 32)) +#define BYTESWAP8(x) x = SHA_HTONLL(x) + +#else /* no long long */ + +#if defined(IS_LITTLE_ENDIAN) +#define ULLC(hi,lo) { 0x ## lo ## U, 0x ## hi ## U } +#else +#define ULLC(hi,lo) { 0x ## hi ## U, 0x ## lo ## U } +#endif + +#define SHA_HTONLL(x) ( BYTESWAP4(x.lo), BYTESWAP4(x.hi), \ + x.hi ^= x.lo ^= x.hi ^= x.lo, x) +#define BYTESWAP8(x) do { PRUint32 tmp; BYTESWAP4(x.lo); BYTESWAP4(x.hi); \ + tmp = x.lo; x.lo = x.hi; x.hi = tmp; } while (0) +#endif + +/* SHA-384 and SHA-512 constants, K512. */ +static const PRUint64 K512[80] = { +#if PR_BYTES_PER_LONG == 8 + 0x428a2f98d728ae22UL , 0x7137449123ef65cdUL , + 0xb5c0fbcfec4d3b2fUL , 0xe9b5dba58189dbbcUL , + 0x3956c25bf348b538UL , 0x59f111f1b605d019UL , + 0x923f82a4af194f9bUL , 0xab1c5ed5da6d8118UL , + 0xd807aa98a3030242UL , 0x12835b0145706fbeUL , + 0x243185be4ee4b28cUL , 0x550c7dc3d5ffb4e2UL , + 0x72be5d74f27b896fUL , 0x80deb1fe3b1696b1UL , + 0x9bdc06a725c71235UL , 0xc19bf174cf692694UL , + 0xe49b69c19ef14ad2UL , 0xefbe4786384f25e3UL , + 0x0fc19dc68b8cd5b5UL , 0x240ca1cc77ac9c65UL , + 0x2de92c6f592b0275UL , 0x4a7484aa6ea6e483UL , + 0x5cb0a9dcbd41fbd4UL , 0x76f988da831153b5UL , + 0x983e5152ee66dfabUL , 0xa831c66d2db43210UL , + 0xb00327c898fb213fUL , 0xbf597fc7beef0ee4UL , + 0xc6e00bf33da88fc2UL , 0xd5a79147930aa725UL , + 0x06ca6351e003826fUL , 0x142929670a0e6e70UL , + 0x27b70a8546d22ffcUL , 0x2e1b21385c26c926UL , + 0x4d2c6dfc5ac42aedUL , 0x53380d139d95b3dfUL , + 0x650a73548baf63deUL , 0x766a0abb3c77b2a8UL , + 0x81c2c92e47edaee6UL , 0x92722c851482353bUL , + 0xa2bfe8a14cf10364UL , 0xa81a664bbc423001UL , + 0xc24b8b70d0f89791UL , 0xc76c51a30654be30UL , + 0xd192e819d6ef5218UL , 0xd69906245565a910UL , + 0xf40e35855771202aUL , 0x106aa07032bbd1b8UL , + 0x19a4c116b8d2d0c8UL , 0x1e376c085141ab53UL , + 0x2748774cdf8eeb99UL , 0x34b0bcb5e19b48a8UL , + 0x391c0cb3c5c95a63UL , 0x4ed8aa4ae3418acbUL , + 0x5b9cca4f7763e373UL , 0x682e6ff3d6b2b8a3UL , + 0x748f82ee5defb2fcUL , 0x78a5636f43172f60UL , + 0x84c87814a1f0ab72UL , 0x8cc702081a6439ecUL , + 0x90befffa23631e28UL , 0xa4506cebde82bde9UL , + 0xbef9a3f7b2c67915UL , 0xc67178f2e372532bUL , + 0xca273eceea26619cUL , 0xd186b8c721c0c207UL , + 0xeada7dd6cde0eb1eUL , 0xf57d4f7fee6ed178UL , + 0x06f067aa72176fbaUL , 0x0a637dc5a2c898a6UL , + 0x113f9804bef90daeUL , 0x1b710b35131c471bUL , + 0x28db77f523047d84UL , 0x32caab7b40c72493UL , + 0x3c9ebe0a15c9bebcUL , 0x431d67c49c100d4cUL , + 0x4cc5d4becb3e42b6UL , 0x597f299cfc657e2aUL , + 0x5fcb6fab3ad6faecUL , 0x6c44198c4a475817UL +#else + ULLC(428a2f98,d728ae22), ULLC(71374491,23ef65cd), + ULLC(b5c0fbcf,ec4d3b2f), ULLC(e9b5dba5,8189dbbc), + ULLC(3956c25b,f348b538), ULLC(59f111f1,b605d019), + ULLC(923f82a4,af194f9b), ULLC(ab1c5ed5,da6d8118), + ULLC(d807aa98,a3030242), ULLC(12835b01,45706fbe), + ULLC(243185be,4ee4b28c), ULLC(550c7dc3,d5ffb4e2), + ULLC(72be5d74,f27b896f), ULLC(80deb1fe,3b1696b1), + ULLC(9bdc06a7,25c71235), ULLC(c19bf174,cf692694), + ULLC(e49b69c1,9ef14ad2), ULLC(efbe4786,384f25e3), + ULLC(0fc19dc6,8b8cd5b5), ULLC(240ca1cc,77ac9c65), + ULLC(2de92c6f,592b0275), ULLC(4a7484aa,6ea6e483), + ULLC(5cb0a9dc,bd41fbd4), ULLC(76f988da,831153b5), + ULLC(983e5152,ee66dfab), ULLC(a831c66d,2db43210), + ULLC(b00327c8,98fb213f), ULLC(bf597fc7,beef0ee4), + ULLC(c6e00bf3,3da88fc2), ULLC(d5a79147,930aa725), + ULLC(06ca6351,e003826f), ULLC(14292967,0a0e6e70), + ULLC(27b70a85,46d22ffc), ULLC(2e1b2138,5c26c926), + ULLC(4d2c6dfc,5ac42aed), ULLC(53380d13,9d95b3df), + ULLC(650a7354,8baf63de), ULLC(766a0abb,3c77b2a8), + ULLC(81c2c92e,47edaee6), ULLC(92722c85,1482353b), + ULLC(a2bfe8a1,4cf10364), ULLC(a81a664b,bc423001), + ULLC(c24b8b70,d0f89791), ULLC(c76c51a3,0654be30), + ULLC(d192e819,d6ef5218), ULLC(d6990624,5565a910), + ULLC(f40e3585,5771202a), ULLC(106aa070,32bbd1b8), + ULLC(19a4c116,b8d2d0c8), ULLC(1e376c08,5141ab53), + ULLC(2748774c,df8eeb99), ULLC(34b0bcb5,e19b48a8), + ULLC(391c0cb3,c5c95a63), ULLC(4ed8aa4a,e3418acb), + ULLC(5b9cca4f,7763e373), ULLC(682e6ff3,d6b2b8a3), + ULLC(748f82ee,5defb2fc), ULLC(78a5636f,43172f60), + ULLC(84c87814,a1f0ab72), ULLC(8cc70208,1a6439ec), + ULLC(90befffa,23631e28), ULLC(a4506ceb,de82bde9), + ULLC(bef9a3f7,b2c67915), ULLC(c67178f2,e372532b), + ULLC(ca273ece,ea26619c), ULLC(d186b8c7,21c0c207), + ULLC(eada7dd6,cde0eb1e), ULLC(f57d4f7f,ee6ed178), + ULLC(06f067aa,72176fba), ULLC(0a637dc5,a2c898a6), + ULLC(113f9804,bef90dae), ULLC(1b710b35,131c471b), + ULLC(28db77f5,23047d84), ULLC(32caab7b,40c72493), + ULLC(3c9ebe0a,15c9bebc), ULLC(431d67c4,9c100d4c), + ULLC(4cc5d4be,cb3e42b6), ULLC(597f299c,fc657e2a), + ULLC(5fcb6fab,3ad6faec), ULLC(6c44198c,4a475817) +#endif +}; + +struct SHA512ContextStr { + union { + PRUint64 w[80]; /* message schedule, input buffer, plus 64 words */ + PRUint32 l[160]; + PRUint8 b[640]; + } u; + PRUint64 h[8]; /* 8 state variables */ + PRUint64 sizeLo; /* 64-bit count of hashed bytes. */ +}; + +/* =========== SHA512 implementation ===================================== */ + +/* SHA-512 initial hash values */ +static const PRUint64 H512[8] = { +#if PR_BYTES_PER_LONG == 8 + 0x6a09e667f3bcc908UL , 0xbb67ae8584caa73bUL , + 0x3c6ef372fe94f82bUL , 0xa54ff53a5f1d36f1UL , + 0x510e527fade682d1UL , 0x9b05688c2b3e6c1fUL , + 0x1f83d9abfb41bd6bUL , 0x5be0cd19137e2179UL +#else + ULLC(6a09e667,f3bcc908), ULLC(bb67ae85,84caa73b), + ULLC(3c6ef372,fe94f82b), ULLC(a54ff53a,5f1d36f1), + ULLC(510e527f,ade682d1), ULLC(9b05688c,2b3e6c1f), + ULLC(1f83d9ab,fb41bd6b), ULLC(5be0cd19,137e2179) +#endif +}; + + +SHA512Context * +SHA512_NewContext(void) +{ + SHA512Context *ctx = PORT_New(SHA512Context); + return ctx; +} + +void +SHA512_DestroyContext(SHA512Context *ctx, PRBool freeit) +{ + if (freeit) { + PORT_ZFree(ctx, sizeof *ctx); + } +} + +void +SHA512_Begin(SHA512Context *ctx) +{ + memset(ctx, 0, sizeof *ctx); + memcpy(H, H512, sizeof H512); +} + +#if defined(SHA512_TRACE) +#if defined(HAVE_LONG_LONG) +#define DUMP(n,a,d,e,h) printf(" t = %2d, %s = %016lx, %s = %016lx\n", \ + n, #e, d, #a, h); +#else +#define DUMP(n,a,d,e,h) printf(" t = %2d, %s = %08x%08x, %s = %08x%08x\n", \ + n, #e, d.hi, d.lo, #a, h.hi, h.lo); +#endif +#else +#define DUMP(n,a,d,e,h) +#endif + +#if defined(HAVE_LONG_LONG) + +#define ADDTO(x,y) y += x + +#define INITW(t) W[t] = (s1(W[t-2]) + W[t-7] + s0(W[t-15]) + W[t-16]) + +#define ROUND(n,a,b,c,d,e,f,g,h) \ + h += S1(e) + Ch(e,f,g) + K512[n] + W[n]; \ + d += h; \ + h += S0(a) + Maj(a,b,c); \ + DUMP(n,a,d,e,h) + +#else /* use only 32-bit variables, and don't unroll loops */ + +#undef NOUNROLL512 +#define NOUNROLL512 1 + +#define ADDTO(x,y) y.lo += x.lo; y.hi += x.hi + (x.lo > y.lo) + +#define ROTR64a(x,n,lo,hi) (x.lo >> n | x.hi << (32-n)) +#define ROTR64A(x,n,lo,hi) (x.lo << (64-n) | x.hi >> (n-32)) +#define SHR64a(x,n,lo,hi) (x.lo >> n | x.hi << (32-n)) + +/* Capitol Sigma and lower case sigma functions */ +#define s0lo(x) (ROTR64a(x,1,lo,hi) ^ ROTR64a(x,8,lo,hi) ^ SHR64a(x,7,lo,hi)) +#define s0hi(x) (ROTR64a(x,1,hi,lo) ^ ROTR64a(x,8,hi,lo) ^ (x.hi >> 7)) + +#define s1lo(x) (ROTR64a(x,19,lo,hi) ^ ROTR64A(x,61,lo,hi) ^ SHR64a(x,6,lo,hi)) +#define s1hi(x) (ROTR64a(x,19,hi,lo) ^ ROTR64A(x,61,hi,lo) ^ (x.hi >> 6)) + +#define S0lo(x)(ROTR64a(x,28,lo,hi) ^ ROTR64A(x,34,lo,hi) ^ ROTR64A(x,39,lo,hi)) +#define S0hi(x)(ROTR64a(x,28,hi,lo) ^ ROTR64A(x,34,hi,lo) ^ ROTR64A(x,39,hi,lo)) + +#define S1lo(x)(ROTR64a(x,14,lo,hi) ^ ROTR64a(x,18,lo,hi) ^ ROTR64A(x,41,lo,hi)) +#define S1hi(x)(ROTR64a(x,14,hi,lo) ^ ROTR64a(x,18,hi,lo) ^ ROTR64A(x,41,hi,lo)) + +/* 32-bit versions of Ch and Maj */ +#define Chxx(x,y,z,lo) ((x.lo & y.lo) ^ (~x.lo & z.lo)) +#define Majx(x,y,z,lo) ((x.lo & y.lo) ^ (x.lo & z.lo) ^ (y.lo & z.lo)) + +#define INITW(t) \ + do { \ + PRUint32 lo, tm; \ + PRUint32 cy = 0; \ + lo = s1lo(W[t-2]); \ + lo += (tm = W[t-7].lo); if (lo < tm) cy++; \ + lo += (tm = s0lo(W[t-15])); if (lo < tm) cy++; \ + lo += (tm = W[t-16].lo); if (lo < tm) cy++; \ + W[t].lo = lo; \ + W[t].hi = cy + s1hi(W[t-2]) + W[t-7].hi + s0hi(W[t-15]) + W[t-16].hi; \ + } while (0) + +#define ROUND(n,a,b,c,d,e,f,g,h) \ + { \ + PRUint32 lo, tm, cy; \ + lo = S1lo(e); \ + lo += (tm = Chxx(e,f,g,lo)); cy = (lo < tm); \ + lo += (tm = K512[n].lo); if (lo < tm) cy++; \ + lo += (tm = W[n].lo); if (lo < tm) cy++; \ + h.lo += lo; if (h.lo < lo) cy++; \ + h.hi += cy + S1hi(e) + Chxx(e,f,g,hi) + K512[n].hi + W[n].hi; \ + d.lo += h.lo; \ + d.hi += h.hi + (d.lo < h.lo); \ + lo = S0lo(a); \ + lo += (tm = Majx(a,b,c,lo)); cy = (lo < tm); \ + h.lo += lo; if (h.lo < lo) cy++; \ + h.hi += cy + S0hi(a) + Majx(a,b,c,hi); \ + DUMP(n,a,d,e,h) \ + } +#endif + +static void +SHA512_Compress(SHA512Context *ctx) +{ +#if defined(IS_LITTLE_ENDIAN) + { +#if defined(HAVE_LONG_LONG) + PRUint64 t1; +#else + PRUint32 t1; +#endif + BYTESWAP8(W[0]); + BYTESWAP8(W[1]); + BYTESWAP8(W[2]); + BYTESWAP8(W[3]); + BYTESWAP8(W[4]); + BYTESWAP8(W[5]); + BYTESWAP8(W[6]); + BYTESWAP8(W[7]); + BYTESWAP8(W[8]); + BYTESWAP8(W[9]); + BYTESWAP8(W[10]); + BYTESWAP8(W[11]); + BYTESWAP8(W[12]); + BYTESWAP8(W[13]); + BYTESWAP8(W[14]); + BYTESWAP8(W[15]); + } +#endif + + { + PRUint64 t1, t2; +#ifdef NOUNROLL512 + { + /* prepare the "message schedule" */ + int t; + for (t = 16; t < 80; ++t) { + INITW(t); + } + } +#else + INITW(16); + INITW(17); + INITW(18); + INITW(19); + + INITW(20); + INITW(21); + INITW(22); + INITW(23); + INITW(24); + INITW(25); + INITW(26); + INITW(27); + INITW(28); + INITW(29); + + INITW(30); + INITW(31); + INITW(32); + INITW(33); + INITW(34); + INITW(35); + INITW(36); + INITW(37); + INITW(38); + INITW(39); + + INITW(40); + INITW(41); + INITW(42); + INITW(43); + INITW(44); + INITW(45); + INITW(46); + INITW(47); + INITW(48); + INITW(49); + + INITW(50); + INITW(51); + INITW(52); + INITW(53); + INITW(54); + INITW(55); + INITW(56); + INITW(57); + INITW(58); + INITW(59); + + INITW(60); + INITW(61); + INITW(62); + INITW(63); + INITW(64); + INITW(65); + INITW(66); + INITW(67); + INITW(68); + INITW(69); + + INITW(70); + INITW(71); + INITW(72); + INITW(73); + INITW(74); + INITW(75); + INITW(76); + INITW(77); + INITW(78); + INITW(79); +#endif + } +#ifdef SHA512_TRACE + { + int i; + for (i = 0; i < 80; ++i) { +#ifdef HAVE_LONG_LONG + printf("W[%2d] = %016lx\n", i, W[i]); +#else + printf("W[%2d] = %08x%08x\n", i, W[i].hi, W[i].lo); +#endif + } + } +#endif + { + PRUint64 a, b, c, d, e, f, g, h; + + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + +#ifdef NOUNROLL512 + { + int t; + for (t = 0; t < 80; t+= 8) { + ROUND(t+0,a,b,c,d,e,f,g,h) + ROUND(t+1,h,a,b,c,d,e,f,g) + ROUND(t+2,g,h,a,b,c,d,e,f) + ROUND(t+3,f,g,h,a,b,c,d,e) + ROUND(t+4,e,f,g,h,a,b,c,d) + ROUND(t+5,d,e,f,g,h,a,b,c) + ROUND(t+6,c,d,e,f,g,h,a,b) + ROUND(t+7,b,c,d,e,f,g,h,a) + } + } +#else + ROUND( 0,a,b,c,d,e,f,g,h) + ROUND( 1,h,a,b,c,d,e,f,g) + ROUND( 2,g,h,a,b,c,d,e,f) + ROUND( 3,f,g,h,a,b,c,d,e) + ROUND( 4,e,f,g,h,a,b,c,d) + ROUND( 5,d,e,f,g,h,a,b,c) + ROUND( 6,c,d,e,f,g,h,a,b) + ROUND( 7,b,c,d,e,f,g,h,a) + + ROUND( 8,a,b,c,d,e,f,g,h) + ROUND( 9,h,a,b,c,d,e,f,g) + ROUND(10,g,h,a,b,c,d,e,f) + ROUND(11,f,g,h,a,b,c,d,e) + ROUND(12,e,f,g,h,a,b,c,d) + ROUND(13,d,e,f,g,h,a,b,c) + ROUND(14,c,d,e,f,g,h,a,b) + ROUND(15,b,c,d,e,f,g,h,a) + + ROUND(16,a,b,c,d,e,f,g,h) + ROUND(17,h,a,b,c,d,e,f,g) + ROUND(18,g,h,a,b,c,d,e,f) + ROUND(19,f,g,h,a,b,c,d,e) + ROUND(20,e,f,g,h,a,b,c,d) + ROUND(21,d,e,f,g,h,a,b,c) + ROUND(22,c,d,e,f,g,h,a,b) + ROUND(23,b,c,d,e,f,g,h,a) + + ROUND(24,a,b,c,d,e,f,g,h) + ROUND(25,h,a,b,c,d,e,f,g) + ROUND(26,g,h,a,b,c,d,e,f) + ROUND(27,f,g,h,a,b,c,d,e) + ROUND(28,e,f,g,h,a,b,c,d) + ROUND(29,d,e,f,g,h,a,b,c) + ROUND(30,c,d,e,f,g,h,a,b) + ROUND(31,b,c,d,e,f,g,h,a) + + ROUND(32,a,b,c,d,e,f,g,h) + ROUND(33,h,a,b,c,d,e,f,g) + ROUND(34,g,h,a,b,c,d,e,f) + ROUND(35,f,g,h,a,b,c,d,e) + ROUND(36,e,f,g,h,a,b,c,d) + ROUND(37,d,e,f,g,h,a,b,c) + ROUND(38,c,d,e,f,g,h,a,b) + ROUND(39,b,c,d,e,f,g,h,a) + + ROUND(40,a,b,c,d,e,f,g,h) + ROUND(41,h,a,b,c,d,e,f,g) + ROUND(42,g,h,a,b,c,d,e,f) + ROUND(43,f,g,h,a,b,c,d,e) + ROUND(44,e,f,g,h,a,b,c,d) + ROUND(45,d,e,f,g,h,a,b,c) + ROUND(46,c,d,e,f,g,h,a,b) + ROUND(47,b,c,d,e,f,g,h,a) + + ROUND(48,a,b,c,d,e,f,g,h) + ROUND(49,h,a,b,c,d,e,f,g) + ROUND(50,g,h,a,b,c,d,e,f) + ROUND(51,f,g,h,a,b,c,d,e) + ROUND(52,e,f,g,h,a,b,c,d) + ROUND(53,d,e,f,g,h,a,b,c) + ROUND(54,c,d,e,f,g,h,a,b) + ROUND(55,b,c,d,e,f,g,h,a) + + ROUND(56,a,b,c,d,e,f,g,h) + ROUND(57,h,a,b,c,d,e,f,g) + ROUND(58,g,h,a,b,c,d,e,f) + ROUND(59,f,g,h,a,b,c,d,e) + ROUND(60,e,f,g,h,a,b,c,d) + ROUND(61,d,e,f,g,h,a,b,c) + ROUND(62,c,d,e,f,g,h,a,b) + ROUND(63,b,c,d,e,f,g,h,a) + + ROUND(64,a,b,c,d,e,f,g,h) + ROUND(65,h,a,b,c,d,e,f,g) + ROUND(66,g,h,a,b,c,d,e,f) + ROUND(67,f,g,h,a,b,c,d,e) + ROUND(68,e,f,g,h,a,b,c,d) + ROUND(69,d,e,f,g,h,a,b,c) + ROUND(70,c,d,e,f,g,h,a,b) + ROUND(71,b,c,d,e,f,g,h,a) + + ROUND(72,a,b,c,d,e,f,g,h) + ROUND(73,h,a,b,c,d,e,f,g) + ROUND(74,g,h,a,b,c,d,e,f) + ROUND(75,f,g,h,a,b,c,d,e) + ROUND(76,e,f,g,h,a,b,c,d) + ROUND(77,d,e,f,g,h,a,b,c) + ROUND(78,c,d,e,f,g,h,a,b) + ROUND(79,b,c,d,e,f,g,h,a) +#endif + + ADDTO(a,H[0]); + ADDTO(b,H[1]); + ADDTO(c,H[2]); + ADDTO(d,H[3]); + ADDTO(e,H[4]); + ADDTO(f,H[5]); + ADDTO(g,H[6]); + ADDTO(h,H[7]); + } +} + +void +SHA512_Update(SHA512Context *ctx, const unsigned char *input, + unsigned int inputLen) +{ + unsigned int inBuf; + if (!inputLen) + return; + +#if defined(HAVE_LONG_LONG) + inBuf = (unsigned int)ctx->sizeLo & 0x7f; + /* Add inputLen into the count of bytes processed, before processing */ + ctx->sizeLo += inputLen; +#else + inBuf = (unsigned int)ctx->sizeLo.lo & 0x7f; + ctx->sizeLo.lo += inputLen; + if (ctx->sizeLo.lo < inputLen) ctx->sizeLo.hi++; +#endif + + /* if data already in buffer, attemp to fill rest of buffer */ + if (inBuf) { + unsigned int todo = SHA512_BLOCK_LENGTH - inBuf; + if (inputLen < todo) + todo = inputLen; + memcpy(B + inBuf, input, todo); + input += todo; + inputLen -= todo; + if (inBuf + todo == SHA512_BLOCK_LENGTH) + SHA512_Compress(ctx); + } + + /* if enough data to fill one or more whole buffers, process them. */ + while (inputLen >= SHA512_BLOCK_LENGTH) { + memcpy(B, input, SHA512_BLOCK_LENGTH); + input += SHA512_BLOCK_LENGTH; + inputLen -= SHA512_BLOCK_LENGTH; + SHA512_Compress(ctx); + } + /* if data left over, fill it into buffer */ + if (inputLen) + memcpy(B, input, inputLen); +} + +void +SHA512_End(SHA512Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ +#if defined(HAVE_LONG_LONG) + unsigned int inBuf = (unsigned int)ctx->sizeLo & 0x7f; + unsigned int padLen = (inBuf < 112) ? (112 - inBuf) : (112 + 128 - inBuf); + PRUint64 lo, t1; + lo = (ctx->sizeLo << 3); +#else + unsigned int inBuf = (unsigned int)ctx->sizeLo.lo & 0x7f; + unsigned int padLen = (inBuf < 112) ? (112 - inBuf) : (112 + 128 - inBuf); + PRUint64 lo = ctx->sizeLo; + PRUint32 t1; + lo.lo <<= 3; +#endif + + SHA512_Update(ctx, pad, padLen); + +#if defined(HAVE_LONG_LONG) + W[14] = 0; +#else + W[14].lo = 0; + W[14].hi = 0; +#endif + + W[15] = lo; +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP8(W[15]); +#endif + SHA512_Compress(ctx); + + /* now output the answer */ +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP8(H[0]); + BYTESWAP8(H[1]); + BYTESWAP8(H[2]); + BYTESWAP8(H[3]); + BYTESWAP8(H[4]); + BYTESWAP8(H[5]); + BYTESWAP8(H[6]); + BYTESWAP8(H[7]); +#endif + padLen = PR_MIN(SHA512_LENGTH, maxDigestLen); + memcpy(digest, H, padLen); + if (digestLen) + *digestLen = padLen; +} + +SECStatus +SHA512_HashBuf(unsigned char *dest, const unsigned char *src, + uint32 src_length) +{ + SHA512Context ctx; + unsigned int outLen; + + SHA512_Begin(&ctx); + SHA512_Update(&ctx, src, src_length); + SHA512_End(&ctx, dest, &outLen, SHA512_LENGTH); + + return SECSuccess; +} + + +SECStatus +SHA512_Hash(unsigned char *dest, const char *src) +{ + return SHA512_HashBuf(dest, (const unsigned char *)src, PORT_Strlen(src)); +} + + +void SHA512_TraceState(SHA512Context *ctx) { } + +unsigned int +SHA512_FlattenSize(SHA512Context *ctx) +{ + return sizeof *ctx; +} + +SECStatus +SHA512_Flatten(SHA512Context *ctx,unsigned char *space) +{ + PORT_Memcpy(space, ctx, sizeof *ctx); + return SECSuccess; +} + +SHA512Context * +SHA512_Resurrect(unsigned char *space, void *arg) +{ + SHA512Context *ctx = SHA512_NewContext(); + if (ctx) + PORT_Memcpy(ctx, space, sizeof *ctx); + return ctx; +} + +void SHA512_Clone(SHA512Context *dest, SHA512Context *src) +{ + memcpy(dest, src, sizeof *dest); +} + +/* ======================================================================= */ +/* SHA384 uses a SHA512Context as the real context. +** The only differences between SHA384 an SHA512 are: +** a) the intialization values for the context, and +** b) the number of bytes of data produced as output. +*/ + +/* SHA-384 initial hash values */ +static const PRUint64 H384[8] = { +#if PR_BYTES_PER_LONG == 8 + 0xcbbb9d5dc1059ed8UL , 0x629a292a367cd507UL , + 0x9159015a3070dd17UL , 0x152fecd8f70e5939UL , + 0x67332667ffc00b31UL , 0x8eb44a8768581511UL , + 0xdb0c2e0d64f98fa7UL , 0x47b5481dbefa4fa4UL +#else + ULLC(cbbb9d5d,c1059ed8), ULLC(629a292a,367cd507), + ULLC(9159015a,3070dd17), ULLC(152fecd8,f70e5939), + ULLC(67332667,ffc00b31), ULLC(8eb44a87,68581511), + ULLC(db0c2e0d,64f98fa7), ULLC(47b5481d,befa4fa4) +#endif +}; + +SHA384Context * +SHA384_NewContext(void) +{ + return SHA512_NewContext(); +} + +void +SHA384_DestroyContext(SHA384Context *ctx, PRBool freeit) +{ + SHA512_DestroyContext(ctx, freeit); +} + +void +SHA384_Begin(SHA384Context *ctx) +{ + memset(ctx, 0, sizeof *ctx); + memcpy(H, H384, sizeof H384); +} + +void +SHA384_Update(SHA384Context *ctx, const unsigned char *input, + unsigned int inputLen) +{ + SHA512_Update(ctx, input, inputLen); +} + +void +SHA384_End(SHA384Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ +#define SHA_MIN(a,b) (a < b ? a : b) + unsigned int maxLen = SHA_MIN(maxDigestLen, SHA384_LENGTH); + SHA512_End(ctx, digest, digestLen, maxLen); +} + +SECStatus +SHA384_HashBuf(unsigned char *dest, const unsigned char *src, + uint32 src_length) +{ + SHA512Context ctx; + unsigned int outLen; + + SHA384_Begin(&ctx); + SHA512_Update(&ctx, src, src_length); + SHA512_End(&ctx, dest, &outLen, SHA384_LENGTH); + + return SECSuccess; +} + +SECStatus +SHA384_Hash(unsigned char *dest, const char *src) +{ + return SHA384_HashBuf(dest, (const unsigned char *)src, PORT_Strlen(src)); +} + +void SHA384_TraceState(SHA384Context *ctx) { } + +unsigned int +SHA384_FlattenSize(SHA384Context *ctx) +{ + return sizeof(SHA384Context); +} + +SECStatus +SHA384_Flatten(SHA384Context *ctx,unsigned char *space) +{ + return SHA512_Flatten(ctx, space); +} + +SHA384Context * +SHA384_Resurrect(unsigned char *space, void *arg) +{ + return SHA512_Resurrect(space, arg); +} + +void SHA384_Clone(SHA384Context *dest, SHA384Context *src) +{ + memcpy(dest, src, sizeof *dest); +} +#endif /* Comment out unused code. */ + +/* ======================================================================= */ +#ifdef SELFTEST +#include <stdio.h> + +static const char abc[] = { "abc" }; +static const char abcdbc[] = { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +}; +static const char abcdef[] = { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" +}; + +void +dumpHash32(const unsigned char *buf, unsigned int bufLen) +{ + unsigned int i; + for (i = 0; i < bufLen; i += 4) { + printf(" %02x%02x%02x%02x", buf[i], buf[i+1], buf[i+2], buf[i+3]); + } + printf("\n"); +} + +void test256(void) +{ + unsigned char outBuf[SHA256_LENGTH]; + + printf("SHA256, input = %s\n", abc); + SHA256_Hash(outBuf, abc); + dumpHash32(outBuf, sizeof outBuf); + + printf("SHA256, input = %s\n", abcdbc); + SHA256_Hash(outBuf, abcdbc); + dumpHash32(outBuf, sizeof outBuf); +} + +void +dumpHash64(const unsigned char *buf, unsigned int bufLen) +{ + unsigned int i; + for (i = 0; i < bufLen; i += 8) { + if (i % 32 == 0) + printf("\n"); + printf(" %02x%02x%02x%02x%02x%02x%02x%02x", + buf[i ], buf[i+1], buf[i+2], buf[i+3], + buf[i+4], buf[i+5], buf[i+6], buf[i+7]); + } + printf("\n"); +} + +void test512(void) +{ + unsigned char outBuf[SHA512_LENGTH]; + + printf("SHA512, input = %s\n", abc); + SHA512_Hash(outBuf, abc); + dumpHash64(outBuf, sizeof outBuf); + + printf("SHA512, input = %s\n", abcdef); + SHA512_Hash(outBuf, abcdef); + dumpHash64(outBuf, sizeof outBuf); +} + +void time512(void) +{ + unsigned char outBuf[SHA512_LENGTH]; + + SHA512_Hash(outBuf, abc); + SHA512_Hash(outBuf, abcdef); +} + +void test384(void) +{ + unsigned char outBuf[SHA384_LENGTH]; + + printf("SHA384, input = %s\n", abc); + SHA384_Hash(outBuf, abc); + dumpHash64(outBuf, sizeof outBuf); + + printf("SHA384, input = %s\n", abcdef); + SHA384_Hash(outBuf, abcdef); + dumpHash64(outBuf, sizeof outBuf); +} + +int main (int argc, char *argv[], char *envp[]) +{ + int i = 1; + if (argc > 1) { + i = atoi(argv[1]); + } + if (i < 2) { + test256(); + test512(); + test384(); + } else { + while (i-- > 0) { + time512(); + } + printf("done\n"); + } + return 0; +} + +#endif diff --git a/base/third_party/purify/pure.h b/base/third_party/purify/pure.h new file mode 100644 index 0000000..56d4be6 --- /dev/null +++ b/base/third_party/purify/pure.h @@ -0,0 +1,145 @@ +/* + * Header file of Pure API function declarations. + * +* (C) Copyright IBM Corporation. 2006, 2006. All Rights Reserved. + * You may recompile and redistribute these definitions as required. + * + * Version 1.0 + */ + +#ifdef PURIFY + +#if defined(c_plusplus) || defined(__cplusplus) +extern "C" { +#endif + +// Don't include this file directly, use purify.h instead. +// If you need something that's not there, add it. +#ifdef PURIFY_PRIVATE_INCLUDE + +#define PURE_H_VERSION 1 +#include <stddef.h> + +////////////////////////////// +// API's Specific to Purify // +////////////////////////////// + +// TRUE when Purify is running. +int __cdecl PurifyIsRunning(void) ; +// +// Print a string to the viewer. +// +int __cdecl PurePrintf(const char *fmt, ...) ; +int __cdecl PurifyPrintf(const char *fmt, ...) ; +// +// Purify functions for leak and memory-in-use functionalty. +// +size_t __cdecl PurifyNewInuse(void) ; +size_t __cdecl PurifyAllInuse(void) ; +size_t __cdecl PurifyClearInuse(void) ; +size_t __cdecl PurifyNewLeaks(void) ; +size_t __cdecl PurifyAllLeaks(void) ; +size_t __cdecl PurifyClearLeaks(void) ; +// +// Purify functions for handle leakage. +// +size_t __cdecl PurifyAllHandlesInuse(void) ; +size_t __cdecl PurifyNewHandlesInuse(void) ; +// +// Functions that tell you about the state of memory. +// +size_t __cdecl PurifyDescribe(void *addr) ; +size_t __cdecl PurifyWhatColors(void *addr, size_t size) ; +// +// Functions to test the state of memory. If the memory is not +// accessable, an error is signaled just as if there were a memory +// reference and the function returns false. +// +int __cdecl PurifyAssertIsReadable(const void *addr, size_t size) ; // size used to be an int, until IA64 came along +int __cdecl PurifyAssertIsWritable(const void *addr, size_t size) ; +// +// Functions to test the state of memory. If the memory is not +// accessable, these functions return false. No error is signaled. +// +int __cdecl PurifyIsReadable(const void *addr, size_t size) ; +int __cdecl PurifyIsWritable(const void *addr, size_t size) ; +int __cdecl PurifyIsInitialized(const void *addr, size_t size) ; +// +// Functions to set the state of memory. +// +void __cdecl PurifyMarkAsInitialized(void *addr, size_t size) ; +void __cdecl PurifyMarkAsUninitialized(void *addr, size_t size) ; +// +// Functions to do late detection of ABWs, FMWs, IPWs. +// +#define PURIFY_HEAP_CRT (HANDLE) ~(__int64) 1 /* 0xfffffffe */ +#define PURIFY_HEAP_ALL (HANDLE) ~(__int64) 2 /* 0xfffffffd */ +#define PURIFY_HEAP_BLOCKS_LIVE 0x80000000 +#define PURIFY_HEAP_BLOCKS_DEFERRED_FREE 0x40000000 +#define PURIFY_HEAP_BLOCKS_ALL (PURIFY_HEAP_BLOCKS_LIVE|PURIFY_HEAP_BLOCKS_DEFERRED_FREE) +int __cdecl PurifyHeapValidate(unsigned int hHeap, unsigned int dwFlags, const void *addr) ; +int __cdecl PurifySetLateDetectScanCounter(int counter); +int __cdecl PurifySetLateDetectScanInterval(int seconds); +// +// Functions to support pool allocators +// +void __cdecl PurifySetPoolId(const void *mem, int id); +int __cdecl PurifyGetPoolId(const void *mem); +void __cdecl PurifySetUserData(const void *mem, void *data); +void * __cdecl PurifyGetUserData(const void *mem); +void __cdecl PurifyMapPool(int id, void(*fn)()); + + +//////////////////////////////// +// API's Specific to Quantify // +//////////////////////////////// + +// TRUE when Quantify is running. +int __cdecl QuantifyIsRunning(void) ; + +// +// Functions for controlling collection +// +int __cdecl QuantifyDisableRecordingData(void) ; +int __cdecl QuantifyStartRecordingData(void) ; +int __cdecl QuantifyStopRecordingData(void) ; +int __cdecl QuantifyClearData(void) ; +int __cdecl QuantifyIsRecordingData(void) ; + +// Add a comment to the dataset +int __cdecl QuantifyAddAnnotation(char *) ; + +// Save the current data, creating a "checkpoint" dataset +int __cdecl QuantifySaveData(void) ; + +// Set the name of the current thread in the viewer +int __cdecl QuantifySetThreadName(char *) ; + +//////////////////////////////// +// API's Specific to Coverage // +//////////////////////////////// + +// TRUE when Coverage is running. +int __cdecl CoverageIsRunning(void) ; +// +// Functions for controlling collection +// +int __cdecl CoverageDisableRecordingData(void) ; +int __cdecl CoverageStartRecordingData(void) ; +int __cdecl CoverageStopRecordingData(void) ; +int __cdecl CoverageClearData(void) ; +int __cdecl CoverageIsRecordingData(void) ; +// Add a comment to the dataset +int __cdecl CoverageAddAnnotation(char *) ; + +// Save the current data, creating a "checkpoint" dataset +int __cdecl CoverageSaveData(void) ; + + +#endif // PURIFY_PRIVATE_INCLUDE + +#if defined(c_plusplus) || defined(__cplusplus) +} +#endif + +#endif // PURIFY diff --git a/base/third_party/purify/pure_api.c b/base/third_party/purify/pure_api.c new file mode 100644 index 0000000..1248ac3 --- /dev/null +++ b/base/third_party/purify/pure_api.c @@ -0,0 +1,145 @@ +/* + * Header file of Pure API function declarations. + * + * Explicitly no copyright. + * You may recompile and redistribute these definitions as required. + * + * NOTE1: In some situations when compiling with MFC, you should + * enable the setting 'Not using precompiled headers' in Visual C++ + * to avoid a compiler diagnostic. + * + * NOTE2: This file works through the use of deep magic. Calls to functions + * in this file are replaced with calls into the OCI runtime system + * when an instrumented version of this program is run. + * + * NOTE3: The static vars avoidGy_n (where n is a unique number) are used + * to prevent optimizing the functions away when compiler option + * /Gy is set. This is needed so that NOTE2 works properly. + */ +#ifdef PURIFY +#pragma once + extern int errno; +typedef int ptrdiff_t; +typedef unsigned int size_t; +typedef unsigned short wchar_t; +static int avoidGy_1 = 0; +static int avoidGy_2 = 0; +static int avoidGy_3 = 0; +static int avoidGy_4 = 0; +static int avoidGy_5 = 0; +static int avoidGy_6 = 0; +static int avoidGy_7 = 0; +static int avoidGy_8 = 0; +static int avoidGy_9 = 0; +static int avoidGy_10 = 0; +static int avoidGy_11 = 0; +static int avoidGy_12 = 0; +static int avoidGy_13 = 0; +static int avoidGy_14 = 0; +static int avoidGy_15 = 0; +static int avoidGy_16 = 0; +static int avoidGy_17 = 0; +static int avoidGy_18 = 0; +static int avoidGy_19 = 0; +static int avoidGy_20 = 0; +static int avoidGy_21 = 0; +static int avoidGy_22 = 0; +static int avoidGy_23 = 0; +static int avoidGy_24 = 0; +static int avoidGy_25 = 0; +static int avoidGy_26 = 0; +static int avoidGy_27 = 0; +static int avoidGy_28 = 0; +static int avoidGy_29 = 0; +static int avoidGy_30 = 0; +static int avoidGy_31 = 0; +static int avoidGy_32 = 0; +static int avoidGy_33 = 0; +static int avoidGy_34 = 0; +static int avoidGy_35 = 0; +static int avoidGy_36 = 0; +static int avoidGy_37 = 0; +static int avoidGy_38 = 0; +static int avoidGy_39 = 0; +static int avoidGy_40 = 0; +static int avoidGy_41 = 0; +static int avoidGy_42 = 0; +static int avoidGy_43 = 0; +static int avoidGy_44 = 0; +static int avoidGy_45 = 0; +static int avoidGy_46 = 0; +static int avoidGy_47 = 0; +static int avoidGy_48 = 0; +static int avoidGy_49 = 0; +static int avoidGy_50 = 0; +static int avoidGy_51 = 0; +static int avoidGy_52 = 0; +static int avoidGy_53 = 0; +static int avoidGy_54 = 0; +static int avoidGy_55 = 0; +static int avoidGy_56 = 0; +static int avoidGy_57 = 0; +static int avoidGy_58 = 0; +static int avoidGy_59 = 0; +static int avoidGy_60 = 0; +static int avoidGy_61 = 0; +static int avoidGy_62 = 0; +static int avoidGy_63 = 0; +static int avoidGy_64 = 0; +static int avoidGy_65 = 0; +static int avoidGy_PL_01 = 0; +static int avoidGy_PL_02 = 0; +__declspec(dllexport) int __cdecl PurePrintf(const char *fmt, ...) { if(!++avoidGy_1); fmt; return 0; } +__declspec(dllexport) int __cdecl PurifyIsRunning(void) { if(!++avoidGy_2); return 0; } +__declspec(dllexport) int __cdecl PurifyPrintf(const char *fmt, ...) { if(!++avoidGy_3); fmt; return 0; } +__declspec(dllexport) size_t __cdecl PurifyNewInuse(void) { if(!++avoidGy_4); return 0; } +__declspec(dllexport) size_t __cdecl PurifyAllInuse(void) { if(!++avoidGy_5); return 0; } +__declspec(dllexport) size_t __cdecl PurifyClearInuse(void) { if(!++avoidGy_6); return 0; } +__declspec(dllexport) size_t __cdecl PurifyNewLeaks(void) { if(!++avoidGy_7); return 0; } +__declspec(dllexport) size_t __cdecl PurifyAllLeaks(void) { if(!++avoidGy_8); return 0; } +__declspec(dllexport) size_t __cdecl PurifyClearLeaks(void) { if(!++avoidGy_9); return 0; } +__declspec(dllexport) size_t __cdecl PurifyAllHandlesInuse(void) { if(!++avoidGy_10); return 0; } +__declspec(dllexport) size_t __cdecl PurifyNewHandlesInuse(void) { if(!++avoidGy_11); return 0; } +__declspec(dllexport) size_t __cdecl PurifyDescribe(void *addr) { if(!++avoidGy_12); addr; return 0; } +__declspec(dllexport) int __cdecl PurifyWhatColors(void *addr, size_t size) { if(!++avoidGy_13); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyAssertIsReadable(const void *addr, size_t size) { if(!++avoidGy_14); addr; size; return 1; } +__declspec(dllexport) int __cdecl PurifyAssertIsWritable(const void *addr, size_t size) { if(!++avoidGy_15); addr; size; return 1; } +__declspec(dllexport) int __cdecl PurifyIsReadable(const void *addr, size_t size) { if(!++avoidGy_16); addr; size; return 1; } +__declspec(dllexport) int __cdecl PurifyIsWritable(const void *addr, size_t size) { if(!++avoidGy_17); addr; size; return 1; } +__declspec(dllexport) int __cdecl PurifyIsInitialized(const void *addr, size_t size) { if(!++avoidGy_18); addr; size; return 1; } +__declspec(dllexport) int __cdecl PurifyRed(void *addr, size_t size) { if(!++avoidGy_19); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyGreen(void *addr, size_t size) { if(!++avoidGy_20); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyYellow(void *addr, size_t size) { if(!++avoidGy_21); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyBlue(void *addr, size_t size) { if(!++avoidGy_22); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyMarkAsInitialized(void *addr, size_t size) { if(!++avoidGy_23); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyMarkAsUninitialized(void *addr, size_t size) { if(!++avoidGy_24); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyMarkForTrap(void *addr, size_t size) { if(!++avoidGy_25); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyMarkForNoTrap(void *addr, size_t size) { if(!++avoidGy_26); addr; size; return 0; } +__declspec(dllexport) int __cdecl PurifyHeapValidate(unsigned int hHeap, unsigned int dwFlags, const void *addr) + { if(!++avoidGy_27); hHeap; dwFlags; addr; return 1; } +__declspec(dllexport) int __cdecl PurifySetLateDetectScanCounter(int counter) { if(!++avoidGy_28); counter; return 0; }; +__declspec(dllexport) int __cdecl PurifySetLateDetectScanInterval(int seconds) { if(!++avoidGy_29); seconds; return 0; }; +__declspec(dllexport) void __cdecl PurifySetPoolId(const void *mem, int id) { if(!++avoidGy_61); mem; id; return; }; +__declspec(dllexport) int __cdecl PurifyGetPoolId(const void *mem) { if(!++avoidGy_62); mem; return 0; }; +__declspec(dllexport) void __cdecl PurifySetUserData(const void *mem, void *data) { if(!++avoidGy_63); mem; data; return; }; +__declspec(dllexport) void * __cdecl PurifyGetUserData(const void *mem) { if(!++avoidGy_64); mem; return 0; }; +__declspec(dllexport) void __cdecl PurifyMapPool(int id, void(*fn)()) { if(!++avoidGy_65); id; fn; return; }; +__declspec(dllexport) int __cdecl CoverageIsRunning(void) { if(!++avoidGy_30); return 0; } +__declspec(dllexport) int __cdecl CoverageDisableRecordingData(void) { if(!++avoidGy_31); return 0; } +__declspec(dllexport) int __cdecl CoverageStartRecordingData(void) { if(!++avoidGy_32); return 0; } +__declspec(dllexport) int __cdecl CoverageStopRecordingData(void) { if(!++avoidGy_33); return 0; } +__declspec(dllexport) int __cdecl CoverageClearData(void) { if(!++avoidGy_34); return 0; } +__declspec(dllexport) int __cdecl CoverageIsRecordingData(void) { if(!++avoidGy_35); return 0; } +__declspec(dllexport) int __cdecl CoverageAddAnnotation(char *str) { if(!++avoidGy_36); str; return 0; } +__declspec(dllexport) int __cdecl CoverageSaveData(void) { if(!++avoidGy_37); return 0; } +__declspec(dllexport) int __cdecl QuantifyIsRunning(void) { if(!++avoidGy_42); return 0; } +__declspec(dllexport) int __cdecl QuantifyDisableRecordingData(void) { if(!++avoidGy_43); return 0; } +__declspec(dllexport) int __cdecl QuantifyStartRecordingData(void) { if(!++avoidGy_44); return 0; } +__declspec(dllexport) int __cdecl QuantifyStopRecordingData(void) { if(!++avoidGy_45); return 0; } +__declspec(dllexport) int __cdecl QuantifyClearData(void) { if(!++avoidGy_46); return 0; } +__declspec(dllexport) int __cdecl QuantifyIsRecordingData(void) { if(!++avoidGy_47); return 0; } +__declspec(dllexport) int __cdecl QuantifyAddAnnotation(char *str) { if(!++avoidGy_48); str; return 0; } +__declspec(dllexport) int __cdecl QuantifySaveData(void) { if(!++avoidGy_49); return 0; } +__declspec(dllexport) int __cdecl QuantifySetThreadName(const char *szName) { if(!++avoidGy_50) ; szName; return 0; } + +#endif // PURIFY diff --git a/base/thread.cc b/base/thread.cc new file mode 100644 index 0000000..6908b9f --- /dev/null +++ b/base/thread.cc @@ -0,0 +1,258 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <process.h> +#include <windows.h> + +#include "base/thread.h" + +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/string_util.h" +#include "base/win_util.h" + +// This class is used when starting a thread. It passes information to the +// thread function. It is referenced counted so we can cleanup the event +// object used to synchronize thread startup properly. +class ThreadStartInfo : public base::RefCountedThreadSafe<ThreadStartInfo> { + public: + Thread* self; + HANDLE start_event; + + explicit ThreadStartInfo(Thread* t) + : self(t), + start_event(CreateEvent(NULL, FALSE, FALSE, NULL)) { + } + + ~ThreadStartInfo() { + CloseHandle(start_event); + } +}; + +// This task is used to trigger the message loop to exit. +class ThreadQuitTask : public Task { + public: + virtual void Run() { + MessageLoop::current()->Quit(); + Thread::SetThreadWasQuitProperly(true); + } +}; + +// Once an object is signaled, quits the current inner message loop. +class QuitOnSignal : public MessageLoop::Watcher { + public: + explicit QuitOnSignal(HANDLE signal) : signal_(signal) { + } + virtual void OnObjectSignaled(HANDLE object) { + DCHECK_EQ(object, signal_); + MessageLoop::current()->WatchObject(signal_, NULL); + MessageLoop::current()->Quit(); + } + private: + HANDLE signal_; + DISALLOW_EVIL_CONSTRUCTORS(QuitOnSignal); +}; + +Thread::Thread(const char *name) + : thread_(NULL), + thread_id_(0), + message_loop_(NULL), + name_(name) { + DCHECK(tls_index_) << "static initializer failed"; +} + +Thread::~Thread() { + Stop(); +} + +// We use this thread-local variable to record whether or not a thread exited +// because its Stop method was called. This allows us to catch cases where +// MessageLoop::Quit() is called directly, which is unexpected when using a +// Thread to setup and run a MessageLoop. +// Note that if we start doing complex stuff in other static initializers +// this could cause problems. +TLSSlot Thread::tls_index_ = ThreadLocalStorage::Alloc(); + +void Thread::SetThreadWasQuitProperly(bool flag) { +#ifndef NDEBUG + ThreadLocalStorage::Set(tls_index_, reinterpret_cast<void*>(flag)); +#endif +} + +bool Thread::GetThreadWasQuitProperly() { + bool quit_properly = true; +#ifndef NDEBUG + quit_properly = (ThreadLocalStorage::Get(tls_index_) != 0); +#endif + return quit_properly; +} + +// The information on how to set the thread name comes from +// a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx +#define MS_VC_EXCEPTION 0x406D1388 + +typedef struct tagTHREADNAME_INFO { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. +} THREADNAME_INFO; + + +// On XP, you can only get the ThreadId of the current +// thread. So it is expected that you'll call this after the +// thread starts up; hence, it is static. +void Thread::SetThreadName(const char* name, DWORD tid) { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = tid; + info.dwFlags = 0; + + __try { + RaiseException(MS_VC_EXCEPTION, 0, + sizeof(info)/sizeof(DWORD), + reinterpret_cast<DWORD*>(&info)); + } __except(EXCEPTION_CONTINUE_EXECUTION) { + } +} + + +bool Thread::Start() { + return StartWithStackSize(0); +} + +bool Thread::StartWithStackSize(size_t stack_size) { + DCHECK(!thread_id_ && !thread_); + SetThreadWasQuitProperly(false); + scoped_refptr<ThreadStartInfo> info = new ThreadStartInfo(this); + + unsigned int flags = 0; + if (win_util::GetWinVersion() >= win_util::WINVERSION_XP && stack_size) { + flags = STACK_SIZE_PARAM_IS_A_RESERVATION; + } else { + stack_size = 0; + } + thread_ = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, + static_cast<unsigned int>(stack_size), + ThreadFunc, + info, + flags, + &thread_id_)); + if (!thread_) { + DLOG(ERROR) << "failed to create thread"; + return false; + } + + // Wait for the thread to start and initialize message_loop_ + WaitForSingleObject(info->start_event, INFINITE); + return true; +} + +void Thread::Stop() { + InternalStop(false); +} + +void Thread::NonBlockingStop() { + InternalStop(true); +} + +void Thread::InternalStop(bool run_message_loop) { + if (!thread_) + return; + + DCHECK_NE(thread_id_, GetCurrentThreadId()) << "Can't call Stop() on itself"; + + // We had better have a message loop at this point! If we do not, then it + // most likely means that the thread terminated unexpectedly, probably due + // to someone calling Quit() on our message loop directly. + DCHECK(message_loop_); + + message_loop_->PostTask(FROM_HERE, new ThreadQuitTask()); + + if (run_message_loop) { + QuitOnSignal signal_watcher(thread_); + MessageLoop::current()->WatchObject(thread_, &signal_watcher); + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + // Restore task state. + MessageLoop::current()->SetNestableTasksAllowed(old_state); + } else { + // Wait for the thread to exit. + WaitForSingleObject(thread_, INFINITE); + } + CloseHandle(thread_); + + // Reset state. + thread_ = NULL; + message_loop_ = NULL; +} + +/*static*/ +unsigned __stdcall Thread::ThreadFunc(void* param) { + // The message loop for this thread. + MessageLoop message_loop; + + Thread* self; + + // Complete the initialization of our Thread object. We grab an owning + // reference to the ThreadStartInfo object to ensure that the start_event + // object is not prematurely closed. + { + scoped_refptr<ThreadStartInfo> info = static_cast<ThreadStartInfo*>(param); + self = info->self; + self->message_loop_ = &message_loop; + SetThreadName(self->thread_name().c_str(), GetCurrentThreadId()); + message_loop.SetThreadName(self->thread_name()); + SetEvent(info->start_event); + } + + // Let the thread do extra initialization. + self->Init(); + + message_loop.Run(); + + // Let the thread do extra cleanup. + self->CleanUp(); + + // Assert that MessageLoop::Quit was called by ThreadQuitTask. + DCHECK(Thread::GetThreadWasQuitProperly()); + + // Clearing this here should be unnecessary since we should only be here if + // Thread::Stop was called (the above DCHECK asserts that). However, to help + // make it easier to track down problems in release builds, we null out the + // message_loop_ pointer. + self->message_loop_ = NULL; + + // We can't receive messages anymore. + self->thread_id_ = 0; + return 0; +} diff --git a/base/thread.h b/base/thread.h new file mode 100644 index 0000000..7ed2402 --- /dev/null +++ b/base/thread.h @@ -0,0 +1,135 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_THREAD_H__ +#define BASE_THREAD_H__ + +#include <wtypes.h> +#include <string> + +#include "base/basictypes.h" +#include "base/thread_local_storage.h" + +class MessageLoop; + +// A simple thread abstraction that establishes a MessageLoop on a new thread. +// The consumer uses the MessageLoop of the thread to cause code to execute on +// the thread. When this object is destroyed the thread is terminated. All +// pending tasks queued on the thread's message loop will run to completion +// before the thread is terminated. +// +class Thread { + public: + // Constructor. + // name is a display string to identify the thread. + explicit Thread(const char *name); + + // Destroys the thread, stopping it if necessary. + // + virtual ~Thread(); + + // Starts the thread. Returns true if the thread was successfully started; + // otherwise, returns false. Upon successful return, the message_loop() + // getter will return non-null. + // + bool Start(); + + // Starts the thread. Behaves exactly like Start in addition to allow to + // override the default process stack size. This is not the initial stack size + // but the maximum stack size that thread is allowed to use. + bool StartWithStackSize(size_t stack_size); + + // Signals the thread to exit and returns once the thread has exited. After + // this method returns, the Thread object is completely reset and may be used + // as if it were newly constructed (i.e., Start may be called again). + // + // Stop may be called multiple times and is simply ignored if the thread is + // already stopped. + // + // NOTE: This method is optional. It is not strictly necessary to call this + // method as the Thread's destructor will take care of stopping the thread if + // necessary. + // + void Stop(); + + // Signals the thread to exit and returns once the thread has exited. + // Meanwhile, a message loop is run. After this method returns, the Thread + // object is completely reset and may be used as if it were newly constructed + // (i.e., Start may be called again). + // + // NonBlockingStop may be called multiple times and is simply ignored if the + // thread is already stopped. + // + // NOTE: The behavior is the same as Stop() except that pending tasks are run. + void NonBlockingStop(); + + // Returns the message loop for this thread. Use the MessageLoop's + // Posttask methods to execute code on the thread. This only returns + // non-null after a successful call to Start. After Stop has been called, + // this will return NULL. + // + // NOTE: You must not call this MessageLoop's Quit method directly. Use + // the Thread's Stop method instead. + // + MessageLoop* message_loop() const { return message_loop_; } + + // Set the name of this thread (for display in debugger too). + const std::string &thread_name() { return name_; } + + static void SetThreadWasQuitProperly(bool flag); + static bool GetThreadWasQuitProperly(); + + // Sets the thread name if a debugger is currently attached. Has no effect + // otherwise. To set the name of the current thread, pass GetCurrentThreadId() + // as the tid parameter. + static void SetThreadName(const char* name, DWORD tid); + + protected: + // Called just prior to starting the message loop + virtual void Init() { } + + // Called just after the message loop ends + virtual void CleanUp() { } + + // Stops the thread. Called by either Stop() or NonBlockingStop(). + void InternalStop(bool run_message_loop); + + private: + static unsigned __stdcall ThreadFunc(void* param); + + HANDLE thread_; + unsigned thread_id_; + MessageLoop* message_loop_; + std::string name_; + static TLSSlot tls_index_; + + DISALLOW_EVIL_CONSTRUCTORS(Thread); +}; + +#endif // BASE_THREAD_H__ diff --git a/base/thread_local_storage.h b/base/thread_local_storage.h new file mode 100644 index 0000000..e27edb8 --- /dev/null +++ b/base/thread_local_storage.h @@ -0,0 +1,95 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_THREAD_LOCAL_STORAGE_H__ +#define BASE_THREAD_LOCAL_STORAGE_H__ + +#include "base/basictypes.h" + +#ifdef WIN32 +typedef int TLSSlot; +#else +typedef pthread_key_t TLSSlot; +#endif + +// Wrapper for thread local storage. This class doesn't +// do much except provide an API for portability later. +class ThreadLocalStorage { + public: + // Prototype for the TLS destructor function, which can be + // optionally used to cleanup thread local storage on + // thread exit. 'value' is the data that is stored + // in thread local storage. + typedef void (*TLSDestructorFunc)(void* value); + + // Allocate a TLS 'slot'. + // 'destructor' is a pointer to a function to perform + // per-thread cleanup of this object. If set to NULL, + // no cleanup is done for this TLS slot. + // Returns an index > 0 on success, or -1 on failure. + static TLSSlot Alloc(TLSDestructorFunc destructor = NULL); + + // Free a previously allocated TLS 'slot'. + // If a destructor was set for this slot, removes + // the destructor so that remaining threads exiting + // will not free data. + static void Free(TLSSlot slot); + + // Get the thread-local value stored in slot 'slot'. + // Values are guaranteed to initially be zero. + static void* Get(TLSSlot slot); + + // Set the thread-local value stored in slot 'slot' to + // value 'value'. + static void Set(TLSSlot slot, void* value); + +#ifdef WIN32 + // Function called when on thread exit to call TLS + // destructor functions. This function is used internally. + static void ThreadExit(); + + private: + // Function to lazily initialize our thread local storage. + static void **Initialize(); + + private: + // The maximum number of 'slots' in our thread local storage stack. + // For now, this is fixed. We could either increase statically, or + // we could make it dynamic in the future. + static const int kThreadLocalStorageSize = 64; + + static long tls_key_; + static long tls_max_; + static TLSDestructorFunc tls_destructors_[kThreadLocalStorageSize]; +#endif + + DISALLOW_EVIL_CONSTRUCTORS(ThreadLocalStorage); +}; + +#endif // BASE_THREAD_LOCAL_STORAGE_H__ diff --git a/base/thread_local_storage_unittest.cc b/base/thread_local_storage_unittest.cc new file mode 100644 index 0000000..fff0d2a --- /dev/null +++ b/base/thread_local_storage_unittest.cc @@ -0,0 +1,109 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <process.h> + +#include "base/thread_local_storage.h" +#include "testing/gtest/include/gtest/gtest.h" + +// ignore warnings about ptr->int conversions that we use when +// storing ints into ThreadLocalStorage. +#pragma warning(disable : 4311 4312) + +namespace { + class ThreadLocalStorageTest : public testing::Test { + }; +} + + +TEST(ThreadLocalStorageTest, Basics) { + int index = ThreadLocalStorage::Alloc(); + ThreadLocalStorage::Set(index, reinterpret_cast<void*>(123)); + int value = reinterpret_cast<int>(ThreadLocalStorage::Get(index)); + EXPECT_EQ(value, 123); +} + +const int kInitialTlsValue = 0x5555; +static int tls_index = 0; + +unsigned __stdcall TLSTestThreadMain(void* param) { + // param contains the thread local storage index. + int *index = reinterpret_cast<int*>(param); + *index = kInitialTlsValue; + + ThreadLocalStorage::Set(tls_index, index); + + int *ptr = static_cast<int*>(ThreadLocalStorage::Get(tls_index)); + EXPECT_EQ(ptr, index); + EXPECT_EQ(*ptr, kInitialTlsValue); + *index = 0; + + ptr = static_cast<int*>(ThreadLocalStorage::Get(tls_index)); + EXPECT_EQ(ptr, index); + EXPECT_EQ(*ptr, 0); + return 0; +} + +void ThreadLocalStorageCleanup(void *value) { + int *ptr = reinterpret_cast<int*>(value); + if (ptr) + *ptr = kInitialTlsValue; +} + + +TEST(ThreadLocalStorageTest, TLSDestructors) { + // Create a TLS index with a destructor. Create a set of + // threads that set the TLS, while the destructor cleans it up. + // After the threads finish, verify that the value is cleaned up. + const int kNumThreads = 5; + HANDLE threads[kNumThreads]; + int values[kNumThreads]; + + tls_index = ThreadLocalStorage::Alloc(ThreadLocalStorageCleanup); + + // Spawn the threads. + for (int16 index = 0; index < kNumThreads; index++) { + values[index] = kInitialTlsValue; + void *argument = static_cast<void*>(&(values[index])); + unsigned thread_id; + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, TLSTestThreadMain, argument, 0, &thread_id)); + EXPECT_NE(threads[index], (HANDLE)NULL); + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], 60*1000); + EXPECT_EQ(rv, WAIT_OBJECT_0); // verify all threads finished + + // verify that the destructor was called and that we reset. + EXPECT_EQ(values[index], kInitialTlsValue); + } +} diff --git a/base/thread_local_storage_win.cc b/base/thread_local_storage_win.cc new file mode 100644 index 0000000..7fba9ef --- /dev/null +++ b/base/thread_local_storage_win.cc @@ -0,0 +1,177 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "base/thread_local_storage.h" + +#include "base/logging.h" + +// In order to make TLS destructors work, we need to keep function +// pointers to the destructor for each TLS that we allocate. +// We make this work by allocating a single OS-level TLS, which +// contains an array of slots for the application to use. In +// parallel, we also allocate an array of destructors, which we +// keep track of and call when threads terminate. + +// tls_key_ is the one native TLS that we use. It stores our +// table. +long ThreadLocalStorage::tls_key_ = TLS_OUT_OF_INDEXES; + +// tls_max_ is the high-water-mark of allocated thread local storage. +// We intentionally skip 0 so that it is not confused with an +// unallocated TLS slot. +long ThreadLocalStorage::tls_max_ = 1; + +// An array of destructor function pointers for the slots. If +// a slot has a destructor, it will be stored in its corresponding +// entry in this array. +ThreadLocalStorage::TLSDestructorFunc + ThreadLocalStorage::tls_destructors_[kThreadLocalStorageSize]; + +void **ThreadLocalStorage::Initialize() { + if (tls_key_ == TLS_OUT_OF_INDEXES) { + long value = TlsAlloc(); + DCHECK(value != TLS_OUT_OF_INDEXES); + + // Atomically test-and-set the tls_key. If the key is TLS_OUT_OF_INDEXES, + // go ahead and set it. Otherwise, do nothing, as another + // thread already did our dirty work. + if (InterlockedCompareExchange(&tls_key_, value, TLS_OUT_OF_INDEXES) != + TLS_OUT_OF_INDEXES) { + // We've been shortcut. Another thread replaced tls_key_ first so we need + // to destroy our index and use the one the other thread got first. + TlsFree(value); + } + } + DCHECK(TlsGetValue(tls_key_) == NULL); + + // Create an array to store our data. + void **tls_data = new void*[kThreadLocalStorageSize]; + memset(tls_data, 0, sizeof(void*[kThreadLocalStorageSize])); + TlsSetValue(tls_key_, tls_data); + return tls_data; +} + +TLSSlot ThreadLocalStorage::Alloc(TLSDestructorFunc destructor) { + if (tls_key_ == TLS_OUT_OF_INDEXES || !TlsGetValue(tls_key_)) + Initialize(); + + // Grab a new slot. + int slot = InterlockedIncrement(&tls_max_) - 1; + if (slot >= kThreadLocalStorageSize) { + NOTREACHED(); + return -1; + } + + // Setup our destructor. + tls_destructors_[slot] = destructor; + return slot; +} + +void ThreadLocalStorage::Free(TLSSlot slot) { + // At this time, we don't reclaim old indices for TLS slots. + // So all we need to do is wipe the destructor. + tls_destructors_[slot] = NULL; +} + +void* ThreadLocalStorage::Get(TLSSlot slot) { + void **tls_data = static_cast<void**>(TlsGetValue(tls_key_)); + if (!tls_data) + tls_data = Initialize(); + DCHECK(slot >= 0 && slot < kThreadLocalStorageSize); + return tls_data[slot]; +} + +void ThreadLocalStorage::Set(TLSSlot slot, void* value) { + void **tls_data = static_cast<void**>(TlsGetValue(tls_key_)); + if (!tls_data) + tls_data = Initialize(); + DCHECK(slot >= 0 && slot < kThreadLocalStorageSize); + tls_data[slot] = value; +} + +void ThreadLocalStorage::ThreadExit() { + void **tls_data = static_cast<void**>(TlsGetValue(tls_key_)); + + // Maybe we have never initialized TLS for this thread. + if (!tls_data) + return; + + for (int slot = 0; slot < tls_max_; slot++) { + if (tls_destructors_[slot] != NULL) { + void *value = tls_data[slot]; + tls_destructors_[slot](value); + } + } + + delete[] tls_data; + + // In case there are other "onexit" handlers... + TlsSetValue(tls_key_, NULL); +} + +// Thread Termination Callbacks. +// Windows doesn't support a per-thread destructor with its +// TLS primitives. So, we build it manually by inserting a +// function to be called on each thread's exit. +// This magic is from http://www.codeproject.com/threads/tls.asp +// and it works for VC++ 7.0 and later. + +// This makes the linker create the TLS directory if it's not already +// there. (e.g. if __declspec(thread) is not used). +#pragma comment(linker, "/INCLUDE:__tls_used") + +// Static callback function to call with each thread termination. +void NTAPI OnThreadExit(PVOID module, DWORD reason, PVOID reserved) +{ + // On XP SP0 & SP1, the DLL_PROCESS_ATTACH is never seen. It is sent on SP2+ + // and on W2K and W2K3. So don't assume it is sent. + if (DLL_THREAD_DETACH == reason || DLL_PROCESS_DETACH == reason) + ThreadLocalStorage::ThreadExit(); +} + +// Note: .CRT section get merged with .rdata on x64 so it should be constant +// data. +// +// .CRT$XLA to .CRT$XLZ is an array of PIMAGE_TLS_CALLBACK pointers that are +// called automatically by the OS loader code (not the CRT) when the module is +// loaded and on thread creation. They are NOT called if the module has been +// loaded by a LoadLibrary() call. It must have implicitly been loaded at +// process startup. +// By implicitly loaded, I mean that it is directly referenced by the main EXE +// or by one of its dependent DLLs. Delay-loaded DLL doesn't count as being +// implicitly loaded. +// +// See VC\crt\src\tlssup.c for reference. +#pragma data_seg(".CRT$XLB") +PIMAGE_TLS_CALLBACK p_thread_callback = OnThreadExit; + +// Reset the default section. +#pragma data_seg() diff --git a/base/thread_unittest.cc b/base/thread_unittest.cc new file mode 100644 index 0000000..ba91005 --- /dev/null +++ b/base/thread_unittest.cc @@ -0,0 +1,157 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/lock.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class ThreadTest : public testing::Test { + }; +} + +class ToggleValue : public Task { + public: + explicit ToggleValue(bool* value) : value_(value) { + } + virtual void Run() { + *value_ = !*value_; + } + private: + bool* value_; +}; + +class SleepSome : public Task { + public: + explicit SleepSome(DWORD msec) : msec_(msec) { + } + virtual void Run() { + Sleep(msec_); + } + private: + DWORD msec_; +}; + +TEST(ThreadTest, BasicTest1) { + Thread a("BasicTest1"); + a.Start(); + EXPECT_TRUE(a.message_loop()); + a.Stop(); + EXPECT_FALSE(a.message_loop()); + a.Start(); + EXPECT_TRUE(a.message_loop()); + a.Stop(); + EXPECT_FALSE(a.message_loop()); +} + +TEST(ThreadTest, BasicTest2) { + Thread a("BasicTest2"); + a.Start(); + EXPECT_TRUE(a.message_loop()); + + bool was_invoked = false; + a.message_loop()->PostTask(FROM_HERE, new ToggleValue(&was_invoked)); + + // wait for the task to run (we could use a kernel event here + // instead to avoid busy waiting, but this is sufficient for + // testing purposes). + for (int i = 50; i >= 0 && !was_invoked; --i) { + Sleep(20); + } + EXPECT_TRUE(was_invoked); +} + +TEST(ThreadTest, BasicTest3) { + bool was_invoked = false; + { + Thread a("BasicTest3"); + a.Start(); + EXPECT_TRUE(a.message_loop()); + + // Test that all events are dispatched before the Thread object is + // destroyed. We do this by dispatching a sleep event before the + // event that will toggle our sentinel value. + a.message_loop()->PostTask(FROM_HERE, new SleepSome(500)); + a.message_loop()->PostTask(FROM_HERE, new ToggleValue(&was_invoked)); + } + EXPECT_TRUE(was_invoked); +} + +namespace { + class DummyTask : public Task { + public: + explicit DummyTask(int* counter) : counter_(counter) { + } + void Run() { + // Let's make sure that no other thread is running while we + // are executing this code. The sleeps help us assert that. + int initial_value = *counter_; + Sleep(1); + ++(*counter_); + Sleep(1); + int final_value = *counter_; + DCHECK(final_value == initial_value + 1); + } + private: + int* counter_; + }; +} + +namespace { + // This task just continuously posts events to its thread, keeping it well + // saturated with work. If our thread interlocking is not fair, then we will + // never exit. + class BusyTask : public Task { + public: + explicit BusyTask(HANDLE quit_event) : quit_event_(quit_event) { + } + void Run() { + if (WaitForSingleObject(quit_event_, 0) != WAIT_OBJECT_0) + MessageLoop::current()->PostTask(FROM_HERE, new BusyTask(quit_event_)); + } + private: + HANDLE quit_event_; + }; + + // This task just tries to set the quit sentinel to signal the busy thread + // to stop doing work. + class InterruptBusyTask : public Task { + public: + explicit InterruptBusyTask(HANDLE quit_event) : quit_event_(quit_event) { + } + void Run() { + SetEvent(quit_event_); + } + private: + HANDLE quit_event_; + }; +} + diff --git a/base/time.cc b/base/time.cc new file mode 100644 index 0000000..aa915e6 --- /dev/null +++ b/base/time.cc @@ -0,0 +1,190 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/time.h" +#include "base/string_util.h" +#include "base/third_party/nspr/prtime.h" + +#include "base/logging.h" + +namespace { + +// Time between resampling the un-granular clock for this API. 60 seconds. +const int kMaxMillisecondsToAvoidDrift = 60 * Time::kMillisecondsPerSecond; + +} // namespace + +// TimeDelta ------------------------------------------------------------------ + +// static +TimeDelta TimeDelta::FromDays(int64 days) { + return TimeDelta(days * Time::kMicrosecondsPerDay); +} + +// static +TimeDelta TimeDelta::FromHours(int64 hours) { + return TimeDelta(hours * Time::kMicrosecondsPerHour); +} + +// static +TimeDelta TimeDelta::FromMinutes(int64 minutes) { + return TimeDelta(minutes * Time::kMicrosecondsPerMinute); +} + +// static +TimeDelta TimeDelta::FromSeconds(int64 secs) { + return TimeDelta(secs * Time::kMicrosecondsPerSecond); +} + +// static +TimeDelta TimeDelta::FromMilliseconds(int64 ms) { + return TimeDelta(ms * Time::kMicrosecondsPerMillisecond); +} + +// static +TimeDelta TimeDelta::FromMicroseconds(int64 us) { + return TimeDelta(us); +} + +int TimeDelta::InDays() const { + return static_cast<int>(delta_ / Time::kMicrosecondsPerDay); +} + +int TimeDelta::InHours() const { + return static_cast<int>(delta_ / Time::kMicrosecondsPerHour); +} + +double TimeDelta::InSecondsF() const { + return static_cast<double>(delta_) / Time::kMicrosecondsPerSecond; +} + +int64 TimeDelta::InSeconds() const { + return delta_ / Time::kMicrosecondsPerSecond; +} + +double TimeDelta::InMillisecondsF() const { + return static_cast<double>(delta_) / Time::kMicrosecondsPerMillisecond; +} + +int64 TimeDelta::InMilliseconds() const { + return delta_ / Time::kMicrosecondsPerMillisecond; +} + +int64 TimeDelta::InMicroseconds() const { + return delta_; +} + +// Time ----------------------------------------------------------------------- + +int64 Time::initial_time_ = 0; +TimeTicks Time::initial_ticks_; + +// static +void Time::InitializeClock() +{ + initial_ticks_ = TimeTicks::Now(); + initial_time_ = CurrentWallclockMicroseconds(); +} + +// static +Time Time::Now() { + if (initial_time_ == 0) + InitializeClock(); + + // We implement time using the high-resolution timers so that we can get + // timeouts which are smaller than 10-15ms. If we just used + // CurrentWallclockMicroseconds(), we'd have the less-granular timer. + // + // To make this work, we initialize the clock (initial_time) and the + // counter (initial_ctr). To compute the initial time, we can check + // the number of ticks that have elapsed, and compute the delta. + // + // To avoid any drift, we periodically resync the counters to the system + // clock. + while(true) { + TimeTicks ticks = TimeTicks::Now(); + + // Calculate the time elapsed since we started our timer + TimeDelta elapsed = ticks - initial_ticks_; + + // Check if enough time has elapsed that we need to resync the clock. + if (elapsed.InMilliseconds() > kMaxMillisecondsToAvoidDrift) { + InitializeClock(); + continue; + } + + return elapsed + initial_time_; + } +} + +// static +Time Time::FromTimeT(time_t tt) { + if (tt == 0) + return Time(); // Preserve 0 so we can tell it doesn't exist. + return (tt * kMicrosecondsPerSecond) + kTimeTToMicrosecondsOffset; +} + +time_t Time::ToTimeT() const { + if (us_ == 0) + return 0; // Preserve 0 so we can tell it doesn't exist. + return (us_ - kTimeTToMicrosecondsOffset) / kMicrosecondsPerSecond; +} + +double Time::ToDoubleT() const { + if (us_ == 0) + return 0; // Preserve 0 so we can tell it doesn't exist. + return (static_cast<double>(us_ - kTimeTToMicrosecondsOffset) / + static_cast<double>(kMicrosecondsPerSecond)); +} + +Time Time::LocalMidnight() const { + Exploded exploded; + LocalExplode(&exploded); + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + return FromLocalExploded(exploded); +} + +// static +bool Time::FromString(const wchar_t* time_string, Time* parsed_time) { + DCHECK((time_string != NULL) && (parsed_time != NULL)); + std::string ascii_time_string = WideToUTF8(time_string); + if (ascii_time_string.length() == 0) + return false; + PRTime result_time = 0; + PRStatus result = PR_ParseTimeString(ascii_time_string.c_str(), PR_FALSE, + &result_time); + if (PR_SUCCESS != result) + return false; + result_time += kTimeTToMicrosecondsOffset; + *parsed_time = Time(result_time); + return true; +} diff --git a/base/time.h b/base/time.h new file mode 100644 index 0000000..e0c806d --- /dev/null +++ b/base/time.h @@ -0,0 +1,469 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Time represents an absolute point in time, internally represented as +// microseconds (s/1,000,000) since a platform-dependent epoch. Each +// platform's epoch, along with other system-dependent clock interface +// routines, is defined in time_PLATFORM.cc. +// +// TimeDelta represents a duration of time, internally represented in +// microseconds. +// +// TimeTicks represents an abstract time that is always incrementing for use +// in measuring time durations. It is internally represented in microseconds. +// It can not be converted to a human-readable time, but is guaranteed not to +// decrease (if the user changes the computer clock, Time::Now() may actually +// decrease or jump). +// +// These classes are represented as only a 64-bit value, so they can be +// efficiently passed by value. + +#ifndef BASE_TIME_H__ +#define BASE_TIME_H__ + +#ifdef WIN32 +// For FILETIME in FromFileTime, until it moves to a new converter class. +// See TODO(iyengar) below. +#include <windows.h> +#endif + +#include <time.h> +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +class Time; +class TimeTicks; + +// This unit test does a lot of manual time manipulation. +class PageLoadTrackerUnitTest; + +// TimeDelta ------------------------------------------------------------------ + +class TimeDelta { + public: + TimeDelta() : delta_(0) { + } + + // Converts units of time to TimeDeltas. + static TimeDelta FromDays(int64 days); + static TimeDelta FromHours(int64 hours); + static TimeDelta FromMinutes(int64 minutes); + static TimeDelta FromSeconds(int64 secs); + static TimeDelta FromMilliseconds(int64 ms); + static TimeDelta FromMicroseconds(int64 us); + + // Returns the internal numeric value of the TimeDelta object. Please don't + // use this and do arithmetic on it, as it is more error prone than using the + // provided operators. + int64 ToInternalValue() const { + return delta_; + } + + // Returns the time delta in some unit. The F versions return a floating + // point value, the "regular" versions return a rounded-down value. + int InDays() const; + int InHours() const; + double InSecondsF() const; + int64 InSeconds() const; + double InMillisecondsF() const; + int64 InMilliseconds() const; + int64 InMicroseconds() const; + + TimeDelta& operator=(TimeDelta other) { + delta_ = other.delta_; + return *this; + } + + // Computations with other deltas. + TimeDelta operator+(TimeDelta other) const { + return TimeDelta(delta_ + other.delta_); + } + TimeDelta operator-(TimeDelta other) const { + return TimeDelta(delta_ - other.delta_); + } + + TimeDelta& operator+=(TimeDelta other) { + delta_ += other.delta_; + return *this; + } + TimeDelta& operator-=(TimeDelta other) { + delta_ -= other.delta_; + return *this; + } + TimeDelta operator-() const { + return TimeDelta(-delta_); + } + + // Computations with ints, note that we only allow multiplicative operations + // with ints, and additive operations with other deltas. + TimeDelta operator*(int64 a) const { + return TimeDelta(delta_ * a); + } + TimeDelta operator/(int64 a) const { + return TimeDelta(delta_ / a); + } + TimeDelta& operator*=(int64 a) { + delta_ *= a; + return *this; + } + TimeDelta& operator/=(int64 a) { + delta_ /= a; + return *this; + } + int64 operator/(TimeDelta a) const { + return delta_ / a.delta_; + } + + // Defined below because it depends on the definition of the other classes. + Time operator+(Time t) const; + TimeTicks operator+(TimeTicks t) const; + + // Comparison operators. + bool operator==(TimeDelta other) const { + return delta_ == other.delta_; + } + bool operator!=(TimeDelta other) const { + return delta_ != other.delta_; + } + bool operator<(TimeDelta other) const { + return delta_ < other.delta_; + } + bool operator<=(TimeDelta other) const { + return delta_ <= other.delta_; + } + bool operator>(TimeDelta other) const { + return delta_ > other.delta_; + } + bool operator>=(TimeDelta other) const { + return delta_ >= other.delta_; + } + + private: + friend class Time; + friend class TimeTicks; + friend TimeDelta operator*(int64 a, TimeDelta td); + + // Constructs a delta given the duration in microseconds. This is private + // to avoid confusion by callers with an integer constructor. Use + // FromSeconds, FromMilliseconds, etc. instead. + explicit TimeDelta(int64 delta_us) : delta_(delta_us) { + } + + // Delta in microseconds. + int64 delta_; +}; + +inline TimeDelta operator*(int64 a, TimeDelta td) { + return TimeDelta(a * td.delta_); +} + +// Time ----------------------------------------------------------------------- + +// Represents a wall clock time. +class Time { + public: + static const int64 kMillisecondsPerSecond = 1000; + static const int64 kMicrosecondsPerMillisecond = 1000; + static const int64 kMicrosecondsPerSecond = kMicrosecondsPerMillisecond * + kMillisecondsPerSecond; + static const int64 kMicrosecondsPerMinute = kMicrosecondsPerSecond * 60; + static const int64 kMicrosecondsPerHour = kMicrosecondsPerMinute * 60; + static const int64 kMicrosecondsPerDay = kMicrosecondsPerHour * 24; + static const int64 kMicrosecondsPerWeek = kMicrosecondsPerDay * 7; + + // Represents an exploded time that can be formatted nicely. This is kind of + // like the Win32 SYSTEMTIME structure or the Unix "struct tm" with a few + // additions and changes to prevent errors. + struct Exploded { + int year; // Four digit year "2007" + int month; // 1-based month (values 1 = January, etc.) + int day_of_week; // 0-based day of week (0 = Sunday, etc.) + int day_of_month; // 1-based day of month (1-31) + int hour; // Hour within the current day (0-23) + int minute; // Minute within the current hour (0-59) + int second; // Second within the current minute (0-59 plus leap + // seconds which may take it up to 60). + int millisecond; // Milliseconds within the current second (0-999) + }; + + // Contains the NULL time. Use Time::Now() to get the current time. + explicit Time() : us_(0) { + } + + // Returns true if the time object has not been initialized. + bool is_null() const { + return us_ == 0; + } + + // Returns the current time. Watch out, the system might adjust its clock + // in which case time will actually go backwards. We don't guarantee that + // times are increasing, or that two calls to Now() won't be the same. + static Time Now(); + + // Converts to/from time_t in UTC and a Time class. + // TODO(brettw) this should be removed once everybody starts using the |Time| + // class. + static Time FromTimeT(time_t tt); + time_t ToTimeT() const; + + // Converts time to a double which is the number of seconds since epoch + // (Jan 1, 1970). Webkit uses this format to represent time. + double ToDoubleT() const; + +#ifdef WIN32 + static Time FromFileTime(FILETIME ft); + FILETIME ToFileTime() const; +#endif + + // Converts an exploded structure representing either the local time or UTC + // into a Time class. + static Time FromUTCExploded(const Exploded& exploded) { + return FromExploded(false, exploded); + } + static Time FromLocalExploded(const Exploded& exploded) { + return FromExploded(true, exploded); + } + + // Converts an integer value representing Time to a class. This is used + // when deserializing a |Time| structure, using a value known to be + // compatible. It is not provided as a constructor because the integer type + // may be unclear from the perspective of a caller. + static Time FromInternalValue(int64 us) { + return Time(us); + } + + // Converts a string representation of time to a Time object. + // An example of a time string which is converted is as below:- + // "Tue, 15 Nov 1994 12:45:26 GMT". If the timezone is not specified + // in the input string, we assume local time. + // TODO(iyengar) Move the FromString/FromTimeT/ToTimeT/FromFileTime to + // a new time converter class. + static bool FromString(const wchar_t* time_string, Time* parsed_time); + + // For serializing, use FromInternalValue to reconstitute. Please don't use + // this and do arithmetic on it, as it is more error prone than using the + // provided operators. + int64 ToInternalValue() const { + return us_; + } + + // Fills the given exploded structure with either the local time or UTC from + // this time structure (containing UTC). + void UTCExplode(Exploded* exploded) const { + return Explode(false, exploded); + } + void LocalExplode(Exploded* exploded) const { + return Explode(true, exploded); + } + + // Rounds this time down to the nearest day in local time. It will represent + // midnight on that day. + Time LocalMidnight() const; + + Time& operator=(Time other) { + us_ = other.us_; + return *this; + } + + // Compute the difference between two times. + TimeDelta operator-(Time other) const { + return TimeDelta(us_ - other.us_); + } + + // Modify by some time delta. + Time& operator+=(TimeDelta delta) { + us_ += delta.delta_; + return *this; + } + Time& operator-=(TimeDelta delta) { + us_ -= delta.delta_; + return *this; + } + + // Return a new time modified by some delta. + Time operator+(TimeDelta delta) const { + return us_ + delta.delta_; + } + Time operator-(TimeDelta delta) const { + return us_ - delta.delta_; + } + + // Comparison operators + bool operator==(Time other) const { + return us_ == other.us_; + } + bool operator!=(Time other) const { + return us_ != other.us_; + } + bool operator<(Time other) const { + return us_ < other.us_; + } + bool operator<=(Time other) const { + return us_ <= other.us_; + } + bool operator>(Time other) const { + return us_ > other.us_; + } + bool operator>=(Time other) const { + return us_ >= other.us_; + } + + private: + friend class TimeDelta; + + // Platform-dependent wall clock interface + static int64 CurrentWallclockMicroseconds(); + + // Initialize or resynchronize the clock. + static void InitializeClock(); + + // Explodes the given time to either local time |is_local = true| or UTC + // |is_local = false|. + void Explode(bool is_local, Exploded* exploded) const; + + // Unexplodes a given time assuming the source is either local time + // |is_local = true| or UTC |is_local = false|. + static Time FromExploded(bool is_local, const Exploded& exploded); + + Time(int64 us) : us_(us) { + } + + // The representation of Jan 1, 1970 UTC in microseconds since the + // platform-dependent epoch. + static const int64 kTimeTToMicrosecondsOffset; + + // Time in microseconds in UTC. + int64 us_; + + // The initial time sampled via this API. + static int64 initial_time_; + + // The initial clock counter sampled via this API. + static TimeTicks initial_ticks_; +}; + +inline Time TimeDelta::operator+(Time t) const { + return Time(t.us_ + delta_); +} + +// TimeTicks ------------------------------------------------------------------ + +class TimeTicks { + public: + TimeTicks() : ticks_(0) { + } + + // Platform-dependent tick count representing "right now." + // The resolution of this clock is ~1-5ms. Resolution varies depending + // on hardware/operating system configuration. + static TimeTicks Now(); + + // Returns a platform-dependent high-resolution tick count. IT IS BROKEN ON + // SOME HARDWARE and is designed to be used for profiling and perf testing + // only (see the impl for more information). + static TimeTicks UnreliableHighResNow(); + + + // Returns true if this object has not been initialized. + bool is_null() const { + return ticks_ == 0; + } + + TimeTicks& operator=(TimeTicks other) { + ticks_ = other.ticks_; + return *this; + } + + // Compute the difference between two times. + TimeDelta operator-(TimeTicks other) const { + return TimeDelta(ticks_ - other.ticks_); + } + + // Modify by some time delta. + TimeTicks& operator+=(TimeDelta delta) { + ticks_ += delta.delta_; + return *this; + } + TimeTicks& operator-=(TimeDelta delta) { + ticks_ -= delta.delta_; + return *this; + } + + // Return a new TimeTicks modified by some delta. + TimeTicks operator+(TimeDelta delta) const { + return TimeTicks(ticks_ + delta.delta_); + } + TimeTicks operator-(TimeDelta delta) const { + return TimeTicks(ticks_ - delta.delta_); + } + + // Comparison operators + bool operator==(TimeTicks other) const { + return ticks_ == other.ticks_; + } + bool operator!=(TimeTicks other) const { + return ticks_ != other.ticks_; + } + bool operator<(TimeTicks other) const { + return ticks_ < other.ticks_; + } + bool operator<=(TimeTicks other) const { + return ticks_ <= other.ticks_; + } + bool operator>(TimeTicks other) const { + return ticks_ > other.ticks_; + } + bool operator>=(TimeTicks other) const { + return ticks_ >= other.ticks_; + } + + protected: + friend class TimeDelta; + friend class PageLoadTrackerUnitTest; + + // Please use Now() to create a new object. This is for internal use + // and testing. Ticks is in microseconds. + explicit TimeTicks(int64 ticks) : ticks_(ticks) { + } + + // Tick count in microseconds. + int64 ticks_; + +#ifdef WIN32 + // The function to use for counting ticks. + typedef int (__stdcall *TickFunction)(void); + static TickFunction tick_function_; +#endif +}; + +inline TimeTicks TimeDelta::operator+(TimeTicks t) const { + return TimeTicks(t.ticks_ + delta_); +} + +#endif // BASE_TIME_H__ diff --git a/base/time_mac.cc b/base/time_mac.cc new file mode 100644 index 0000000..bd4be21 --- /dev/null +++ b/base/time_mac.cc @@ -0,0 +1,139 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/time.h" + +#include <mach/mach_time.h> +#include <sys/time.h> +#include <time.h> +#include "base/basictypes.h" +#include "base/logging.h" + +// The Time routines in this file use standard POSIX routines, or almost- +// standard routines in the case of timegm. +// The TimeTicks routines are Mach-specific. + +// Time ----------------------------------------------------------------------- + +// The internal representation of Time uses time_t directly, so there is no +// offset. The epoch is 1970-01-01 00:00:00 UTC. +// static +const int64 Time::kTimeTToMicrosecondsOffset = GG_INT64_C(0); + +// static +int64 Time::CurrentWallclockMicroseconds() { + struct timeval tv; + struct timezone tz = { 0, 0 }; // UTC + if (gettimeofday(&tv, &tz) != 0) { + DCHECK(0) << "Could not determine time of day"; + } + // Combine seconds and microseconds in a 64-bit field containing microseconds + // since the epoch. That's enough for nearly 600 centuries. + return tv.tv_sec * GG_UINT64_C(1000000) + tv.tv_usec; +} + +// static +Time Time::FromExploded(bool is_local, const Exploded& exploded) { + struct tm timestruct; + timestruct.tm_sec = exploded.second; + timestruct.tm_min = exploded.minute; + timestruct.tm_hour = exploded.hour; + timestruct.tm_mday = exploded.day_of_month; + timestruct.tm_mon = exploded.month - 1; + timestruct.tm_year = exploded.year - 1900; + timestruct.tm_wday = exploded.day_of_week; // mktime/timegm ignore this + timestruct.tm_yday = 0; // mktime/timegm ignore this + timestruct.tm_isdst = -1; // attempt to figure it out + timestruct.tm_gmtoff = 0; // not a POSIX field, so mktime/timegm ignore + timestruct.tm_zone = NULL; // not a POSIX field, so mktime/timegm ignore + + time_t seconds; + if (is_local) + seconds = mktime(×truct); + else + seconds = timegm(×truct); + DCHECK(seconds >= 0) << "mktime/timegm could not convert from exploded"; + + uint64 milliseconds = seconds * kMillisecondsPerSecond + exploded.millisecond; + return Time(milliseconds * kMicrosecondsPerMillisecond); +} + +void Time::Explode(bool is_local, Exploded* exploded) const { + // Time stores times with microsecond resolution, but Exploded only carries + // millisecond resolution, so begin by being lossy. + uint64 milliseconds = us_ / kMicrosecondsPerMillisecond; + time_t seconds = milliseconds / kMillisecondsPerSecond; + + struct tm timestruct; + if (is_local) + localtime_r(&seconds, ×truct); + else + gmtime_r(&seconds, ×truct); + + exploded->year = timestruct.tm_year + 1900; + exploded->month = timestruct.tm_mon + 1; + exploded->day_of_week = timestruct.tm_wday; + exploded->day_of_month = timestruct.tm_mday; + exploded->hour = timestruct.tm_hour; + exploded->minute = timestruct.tm_min; + exploded->second = timestruct.tm_sec; + exploded->millisecond = milliseconds % kMillisecondsPerSecond; +} + +// TimeTicks ------------------------------------------------------------------ + +// static +TimeTicks TimeTicks::Now() { + static bool has_timebase_info = false; + static mach_timebase_info_data_t timebase_info; + if (!has_timebase_info) { + has_timebase_info = mach_timebase_info(&timebase_info) == KERN_SUCCESS; + } + DCHECK(has_timebase_info) << "Could not determine system tick rate"; + + // mach_absolute_time is it when it comes to ticks on the Mac. Other calls + // with less precision (such as TickCount) just call through to + // mach_absolute_time. + + // timebase_info converts absolute time tick units into nanoseconds. Divide + // by 1000 to get microseconds up front to stave off overflows. + uint64_t absolute_micro = mach_absolute_time() / 1000 * timebase_info.numer / + timebase_info.denom; + + // Don't bother with the rollover handling that the Windows version does. + // With numer and denom = 1 (the expected case), the 64-bit absolute time + // reported in nanoseconds is enough to last nearly 585 years. + + return TimeTicks(absolute_micro); +} + +// static +TimeTicks TimeTicks::UnreliableHighResNow() { + return Now(); +} diff --git a/base/time_unittest.cc b/base/time_unittest.cc new file mode 100644 index 0000000..214ec50 --- /dev/null +++ b/base/time_unittest.cc @@ -0,0 +1,195 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <process.h> +#include <time.h> + +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Test conversions to/from time_t and exploding/unexploding. +TEST(Time, TimeT) { + // C library time and exploded time. + time_t now_t_1 = time(NULL); + struct tm tms; + localtime_s(&tms, &now_t_1); + + // Convert to ours. + Time our_time_1 = Time::FromTimeT(now_t_1); + Time::Exploded exploded; + our_time_1.LocalExplode(&exploded); + + // This will test both our exploding and our time_t -> Time conversion. + EXPECT_EQ(tms.tm_year + 1900, exploded.year); + EXPECT_EQ(tms.tm_mon + 1, exploded.month); + EXPECT_EQ(tms.tm_mday, exploded.day_of_month); + EXPECT_EQ(tms.tm_hour, exploded.hour); + EXPECT_EQ(tms.tm_min, exploded.minute); + EXPECT_EQ(tms.tm_sec, exploded.second); + + // Convert exploded back to the time struct. + Time our_time_2 = Time::FromLocalExploded(exploded); + EXPECT_TRUE(our_time_1 == our_time_2); + + time_t now_t_2 = our_time_2.ToTimeT(); + EXPECT_EQ(now_t_1, now_t_2); + + // Conversions of 0 should stay 0. + EXPECT_EQ(0, Time().ToTimeT()); + EXPECT_EQ(0, Time::FromTimeT(0).ToInternalValue()); +} + +TEST(Time, ZeroIsSymmetric) { + Time zeroTime(Time::FromTimeT(0)); + EXPECT_EQ(0, zeroTime.ToTimeT()); +} + +TEST(Time, LocalExplode) { + Time a = Time::Now(); + Time::Exploded exploded; + a.LocalExplode(&exploded); + + Time b = Time::FromLocalExploded(exploded); + + // The exploded structure doesn't have microseconds, so the result will be + // rounded to the nearest millisecond. + EXPECT_TRUE((a - b) < TimeDelta::FromMilliseconds(1)); +} + +TEST(Time, UTCExplode) { + Time a = Time::Now(); + Time::Exploded exploded; + a.UTCExplode(&exploded); + + Time b = Time::FromUTCExploded(exploded); + EXPECT_TRUE((a - b) < TimeDelta::FromMilliseconds(1)); +} + +TEST(TimeTicks, Deltas) { + TimeTicks ticks_start = TimeTicks::Now(); + Sleep(10); + TimeTicks ticks_stop = TimeTicks::Now(); + TimeDelta delta = ticks_stop - ticks_start; + EXPECT_GE(delta.InMilliseconds(), 10); + EXPECT_GE(delta.InMicroseconds(), 10000); + EXPECT_EQ(delta.InSeconds(), 0); +} + +namespace { + +class MockTimeTicks : public TimeTicks { + public: + static int Ticker() { + return static_cast<int>(InterlockedIncrement(&ticker_)); + } + + static void InstallTicker() { + old_tick_function_ = tick_function_; + tick_function_ = reinterpret_cast<TickFunction>(&Ticker); + ticker_ = -5; + } + + static void UninstallTicker() { + tick_function_ = old_tick_function_; + } + + private: + static volatile LONG ticker_; + static TickFunction old_tick_function_; +}; + +volatile LONG MockTimeTicks::ticker_; +MockTimeTicks::TickFunction MockTimeTicks::old_tick_function_; + +HANDLE g_rollover_test_start; + +unsigned __stdcall RolloverTestThreadMain(void* param) { + int64 counter = reinterpret_cast<int64>(param); + DWORD rv = WaitForSingleObject(g_rollover_test_start, INFINITE); + EXPECT_EQ(rv, WAIT_OBJECT_0); + + TimeTicks last = TimeTicks::Now(); + for (int index = 0; index < counter; index++) { + TimeTicks now = TimeTicks::Now(); + int64 milliseconds = (now - last).InMilliseconds(); + EXPECT_GT(milliseconds, 0); + EXPECT_LT(milliseconds, 250); + last = now; + } + return 0; +} + +} // namespace + +TEST(TimeTicks, Rollover) { + // The internal counter rolls over at ~49days. We'll use a mock + // timer to test this case. + // Basic test algorithm: + // 1) Set clock to rollover - N + // 2) Create N threads + // 3) Start the threads + // 4) Each thread loops through TimeTicks() N times + // 5) Each thread verifies integrity of result. + + const int kThreads = 8; + // Use int64 so we can cast into a void* without a compiler warning. + const int64 kChecks = 10; + + // It takes a lot of iterations to reproduce the bug! + // (See bug 1081395) + for (int loop = 0; loop < 4096; loop++) { + // Setup + MockTimeTicks::InstallTicker(); + g_rollover_test_start = CreateEvent(0, TRUE, FALSE, 0); + HANDLE threads[kThreads]; + + for (int index = 0; index < kThreads; index++) { + void* argument = reinterpret_cast<void*>(kChecks); + unsigned thread_id; + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, RolloverTestThreadMain, argument, 0, + &thread_id)); + EXPECT_NE((HANDLE)NULL, threads[index]); + } + + // Start! + SetEvent(g_rollover_test_start); + + // Wait for threads to finish + for (int index = 0; index < kThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], INFINITE); + EXPECT_EQ(rv, WAIT_OBJECT_0); + } + + CloseHandle(g_rollover_test_start); + + // Teardown + MockTimeTicks::UninstallTicker(); + } +} diff --git a/base/time_win.cc b/base/time_win.cc new file mode 100644 index 0000000..e7faa86 --- /dev/null +++ b/base/time_win.cc @@ -0,0 +1,244 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/time.h" + +#pragma comment(lib, "winmm.lib") +#include <windows.h> +#include <mmsystem.h> +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/logging.h" + +namespace { + +// From MSDN, FILETIME "Contains a 64-bit value representing the number of +// 100-nanosecond intervals since January 1, 1601 (UTC)." +int64 FileTimeToMicroseconds(const FILETIME& ft) { + // Need to bit_cast to fix alignment, then divide by 10 to convert + // 100-nanoseconds to milliseconds. This only works on little-endian + // machines. + return bit_cast<int64, FILETIME>(ft) / 10; +} + +void MicrosecondsToFileTime(int64 us, FILETIME* ft) { + DCHECK(us >= 0) << "Time is less than 0, negative values are not " + "representable in FILETIME"; + + // Multiply by 10 to convert milliseconds to 100-nanoseconds. Bit_cast will + // handle alignment problems. This only works on little-endian machines. + *ft = bit_cast<FILETIME, int64>(us * 10); +} + +} // namespace + +// Time ----------------------------------------------------------------------- + +// The internal representation of Time uses FILETIME, whose epoch is 1601-01-01 +// 00:00:00 UTC. ((1970-1601)*365+89)*24*60*60*1000*1000, where 89 is the +// number of leap year days between 1601 and 1970: (1970-1601)/4 excluding +// 1700, 1800, and 1900. +// static +const int64 Time::kTimeTToMicrosecondsOffset = GG_INT64_C(11644473600000000); + +// static +int64 Time::CurrentWallclockMicroseconds() { + FILETIME ft; + ::GetSystemTimeAsFileTime(&ft); + return FileTimeToMicroseconds(ft); +} + +// static +Time Time::FromFileTime(FILETIME ft) { + return Time(FileTimeToMicroseconds(ft)); +} + +FILETIME Time::ToFileTime() const { + FILETIME utc_ft; + MicrosecondsToFileTime(us_, &utc_ft); + return utc_ft; +} + +// static +Time Time::FromExploded(bool is_local, const Exploded& exploded) { + // Create the system struct representing our exploded time. It will either be + // in local time or UTC. + SYSTEMTIME st; + st.wYear = exploded.year; + st.wMonth = exploded.month; + st.wDayOfWeek = exploded.day_of_week; + st.wDay = exploded.day_of_month; + st.wHour = exploded.hour; + st.wMinute = exploded.minute; + st.wSecond = exploded.second; + st.wMilliseconds = exploded.millisecond; + + // Convert to FILETIME. + FILETIME ft; + if (!SystemTimeToFileTime(&st, &ft)) { + NOTREACHED() << "Unable to convert time"; + return Time(0); + } + + // Ensure that it's in UTC. + if (is_local) { + FILETIME utc_ft; + LocalFileTimeToFileTime(&ft, &utc_ft); + return Time(FileTimeToMicroseconds(utc_ft)); + } + return Time(FileTimeToMicroseconds(ft)); +} + +void Time::Explode(bool is_local, Exploded* exploded) const { + // FILETIME in UTC. + FILETIME utc_ft; + MicrosecondsToFileTime(us_, &utc_ft); + + // FILETIME in local time if necessary. + BOOL success = TRUE; + FILETIME ft; + if (is_local) + success = FileTimeToLocalFileTime(&utc_ft, &ft); + else + ft = utc_ft; + + // FILETIME in SYSTEMTIME (exploded). + SYSTEMTIME st; + if (!success || !FileTimeToSystemTime(&ft, &st)) { + NOTREACHED() << "Unable to convert time, don't know why"; + ZeroMemory(exploded, sizeof(exploded)); + return; + } + + exploded->year = st.wYear; + exploded->month = st.wMonth; + exploded->day_of_week = st.wDayOfWeek; + exploded->day_of_month = st.wDay; + exploded->hour = st.wHour; + exploded->minute = st.wMinute; + exploded->second = st.wSecond; + exploded->millisecond = st.wMilliseconds; +} + +// TimeTicks ------------------------------------------------------------------ + +TimeTicks::TickFunction TimeTicks::tick_function_= + reinterpret_cast<TickFunction>(&timeGetTime); + +// static +TimeTicks TimeTicks::Now() { + // Uses the multimedia timers on Windows to get a higher resolution clock. + // timeGetTime() provides a resolution which is variable depending on + // hardware and system configuration. It can also be changed by other + // apps. This class does not attempt to change the resolution of the + // timer, because we don't want to affect other applications. + + // timeGetTime() should at least be accurate to ~5ms on all systems. + // timeGetTime() returns a 32-bit millisecond counter which has rollovers + // every ~49 days. + static DWORD last_tick_count = 0; + static int64 tick_rollover_accum = 0; + static Lock* tick_lock = NULL; // To protect during rollover periods. + + // Lazily create the lock we use. + if (!tick_lock) { + Lock* new_lock = new Lock; + if (InterlockedCompareExchangePointer( + reinterpret_cast<PVOID*>(&tick_lock), new_lock, NULL)) { + delete new_lock; + } + } + + // Atomically protect the low and high 32bit values for time. + // In the future we may be able to optimize with + // InterlockedCompareExchange64, but that doesn't work on XP. + DWORD tick_count; + int64 rollover_count; + { + AutoLock lock(*tick_lock); + tick_count = tick_function_(); + if (tick_count < last_tick_count) + tick_rollover_accum += GG_INT64_C(0x100000000); + + last_tick_count = tick_count; + rollover_count = tick_rollover_accum; + } + + // GetTickCount returns milliseconds, we want microseconds. + return TimeTicks((tick_count + rollover_count) * + Time::kMicrosecondsPerMillisecond); +} + +// Overview of time counters: +// (1) CPU cycle counter. (Retrieved via RDTSC) +// The CPU counter provides the highest resolution time stamp and is the least +// expensive to retrieve. However, the CPU counter is unreliable and should not +// be used in production. Its biggest issue is that it is per processor and it +// is not synchronized between processors. Also, on some computers, the counters +// will change frequency due to thermal and power changes, and stop in some +// states. +// +// (2) QueryPerformanceCounter (QPC). The QPC counter provides a high- +// resolution (100 nanoseconds) time stamp but is comparatively more expensive +// to retrieve. What QueryPerformanceCounter actually does is up to the HAL. +// (with some help from ACPI). +// According to http://blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx +// in the worst case, it gets the counter from the rollover interrupt on the +// programmable interrupt timer. In best cases, the HAL may conclude that the +// RDTSC counter runs at a constant frequency, then it uses that instead. On +// multiprocessor machines, it will try to verify the values returned from +// RDTSC on each processor are consistent with each other, and apply a handful +// of workarounds for known buggy hardware. In other words, QPC is supposed to +// give consistent result on a multiprocessor computer, but it is unreliable in +// reality due to bugs in BIOS or HAL on some, especially old computers. +// With recent updates on HAL and newer BIOS, QPC is getting more reliable but +// it should be used with caution. +// +// (3) System time. The system time provides a low-resolution (typically 10ms +// to 55 milliseconds) time stamp but is comparatively less expensive to +// retrieve and more reliable. + +// static +TimeTicks TimeTicks::UnreliableHighResNow() { + // Cached clock frequency -> microseconds. This assumes that the clock + // frequency is faster than one microsecond (which is 1MHz, should be OK). + static int64 ticks_per_microsecond = 0; + + if (ticks_per_microsecond == 0) { + LARGE_INTEGER ticks_per_sec = { 0, 0 }; + if (!QueryPerformanceFrequency(&ticks_per_sec)) + return TimeTicks(0); // Broken, we don't guarantee this function works. + ticks_per_microsecond = + ticks_per_sec.QuadPart / Time::kMicrosecondsPerSecond; + } + + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return TimeTicks(now.QuadPart / ticks_per_microsecond); +} diff --git a/base/timer.cc b/base/timer.cc new file mode 100644 index 0000000..4db8b88 --- /dev/null +++ b/base/timer.cc @@ -0,0 +1,346 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/timer.h" +#include <mmsystem.h> + +#include "base/atomic.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/task.h" + +// Note about hi-resolution timers. +// This class 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 main +// MessageLoop. For the common case, timers will be processed there as the +// message 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 message 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. + +static const wchar_t kWndClass[] = L"Chrome_TimerMessageWindow"; + +static LRESULT CALLBACK MessageWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + if (message == WM_TIMER) { + // Timer not firing? Maybe you're suffering from a WM_PAINTstorm. Make sure + // any WM_PAINT handler you have calls BeginPaint and EndPaint to validate + // the invalid region, otherwise you will be flooded with paint messages + // that trump WM_TIMER when PeekMessage is called. + UINT_PTR timer_id = static_cast<UINT_PTR>(wparam); + TimerManager* tm = reinterpret_cast<TimerManager*>(timer_id); + return tm->MessageWndProc(hwnd, message, wparam, lparam); + } + + return DefWindowProc(hwnd, message, wparam, lparam); +} + +// static +int32 Timer::timer_id_counter_ = 0; + +//----------------------------------------------------------------------------- +// Timer + +Timer::Timer(int delay, Task* task, bool repeating) + : delay_(delay), + task_(task), + repeating_(repeating) { + timer_id_ = base::AtomicIncrement(&timer_id_counter_); + DCHECK(delay >= 0); + Reset(); +} + +void Timer::Reset() { + creation_time_ = Time::Now(); + fire_time_ = creation_time_ + TimeDelta::FromMilliseconds(delay_); + DHISTOGRAM_COUNTS(L"Timer.Durations", delay_); +} + +//----------------------------------------------------------------------------- +// TimerPQueue + +void TimerPQueue::RemoveTimer(Timer* timer) { + const std::vector<Timer*>::iterator location = + find(c.begin(), c.end(), timer); + if (location != c.end()) { + c.erase(location); + make_heap(c.begin(), c.end(), TimerComparison()); + } +} + +bool TimerPQueue::ContainsTimer(const Timer* timer) const { + return find(c.begin(), c.end(), timer) != c.end(); +} + +//----------------------------------------------------------------------------- +// TimerManager + +TimerManager::TimerManager() + : use_broken_delay_(false), + message_hwnd_(NULL), + use_native_timers_(true), + message_loop_(NULL) { + // We've experimented with all sorts of timers, and initially tried + // to avoid using timeBeginPeriod because it does affect the system + // globally. However, after much investigation, it turns out that all + // of the major plugins (flash, windows media 9-11, and quicktime) + // already use timeBeginPeriod to increase the speed of the clock. + // Since the browser must work with these plugins, the browser already + // needs to support a fast clock. We may as well use this ourselves, + // as it really is the best timer mechanism for our needs. + timeBeginPeriod(1); + + // Initialize the Message HWND in the constructor so that the window + // belongs to the same thread as the message loop (this is important!) + GetMessageHWND(); +} + +TimerManager::~TimerManager() { + // Match timeBeginPeriod() from construction. + timeEndPeriod(1); + + if (message_hwnd_ != NULL) + DestroyWindow(message_hwnd_); + + // Be nice to unit tests, and discard and delete all timers along with the + // embedded task objects by handing off to MessageLoop (which would have Run() + // and optionally deleted the objects). + while (timers_.size()) { + Timer* pending = timers_.top(); + timers_.pop(); + message_loop_->DiscardTimer(pending); + } +} + + +Timer* TimerManager::StartTimer(int delay, Task* task, bool repeating) { + Timer* t = new Timer(delay, task, repeating); + StartTimer(t); + return t; +} + +void TimerManager::StopTimer(Timer* timer) { + // Make sure the timer is actually running. + if (!IsTimerRunning(timer)) + return; + // Kill the active timer, and remove the pending entry from the queue. + if (timer != timers_.top()) { + timers_.RemoveTimer(timer); + } else { + timers_.pop(); + UpdateWindowsWmTimer(); // We took away the head of our queue. + } +} + +void TimerManager::ResetTimer(Timer* timer) { + StopTimer(timer); + timer->Reset(); + StartTimer(timer); +} + +bool TimerManager::IsTimerRunning(const Timer* timer) const { + return timers_.ContainsTimer(timer); +} + +Timer* TimerManager::PeekTopTimer() { + if (timers_.empty()) + return NULL; + return timers_.top(); +} + +bool TimerManager::RunSomePendingTimers() { + bool did_work = false; + bool allowed_to_run = message_loop()->NestableTasksAllowed(); + // Process a small group of timers. Cap the maximum number of timers we can + // process so we don't deny cycles to other parts of the process when lots of + // timers have been set. + const int kMaxTimersPerCall = 2; + for (int i = 0; i < kMaxTimersPerCall; ++i) { + if (timers_.empty() || GetCurrentDelay() > 0) + break; + + // Get a pending timer. Deal with updating the timers_ queue and setting + // the TopTimer. We'll execute the timer task only after the timer queue + // is back in a consistent state. + Timer* pending = timers_.top(); + // If pending task isn't invoked_later, then it must be possible to run it + // now (i.e., current task needs to be reentrant). + // TODO(jar): We may block tasks that we can queue from being popped. + if (!message_loop()->NestableTasksAllowed() && + !pending->task()->is_owned_by_message_loop()) + break; + + timers_.pop(); + did_work = true; + + // If the timer is repeating, add it back to the list of timers to process. + if (pending->repeating()) { + pending->Reset(); + timers_.push(pending); + } + + message_loop()->RunTimerTask(pending); + } + + // Restart the WM_TIMER (if necessary). + if (did_work) + UpdateWindowsWmTimer(); + + return did_work; +} + +// Note: Caller is required to call timer->Reset() before calling StartTimer(). +// TODO(jar): change API so that Reset() is called as part of StartTimer, making +// the API a little less error prone. +void TimerManager::StartTimer(Timer* timer) { + // Make sure the timer is not running. + if (IsTimerRunning(timer)) + return; + + timers_.push(timer); // Priority queue will sort the timer into place. + + if (timers_.top() == timer) + UpdateWindowsWmTimer(); // We are new head of queue. +} + +void TimerManager::UpdateWindowsWmTimer() { + if (!use_native_timers_) + return; + + if (timers_.empty()) { + KillTimer(GetMessageHWND(), reinterpret_cast<UINT_PTR>(this)); + return; + } + + int delay = GetCurrentDelay(); + if (delay < USER_TIMER_MINIMUM) + delay = USER_TIMER_MINIMUM; + + // Simulates malfunctioning, early firing timers. Pending tasks should + // only be invoked when the delay they specify has elapsed. + if (use_broken_delay_) + delay = 10; + + // Create a WM_TIMER event that will wake us up to check for any pending + // timers (in case the message loop was otherwise starving us). + SetTimer(GetMessageHWND(), reinterpret_cast<UINT_PTR>(this), delay, NULL); +} + +int TimerManager::GetCurrentDelay() { + if (timers_.empty()) + return -1; + int delay = timers_.top()->current_delay(); + if (delay < 0) + delay = 0; + return delay; +} + +int TimerManager::MessageWndProc(HWND hwnd, UINT message, WPARAM wparam, + LPARAM lparam) { + DCHECK(!lparam); + DCHECK(this == message_loop()->timer_manager()); + if (message_loop()->NestableTasksAllowed()) + RunSomePendingTimers(); + else + UpdateWindowsWmTimer(); + return 0; +} + +MessageLoop* TimerManager::message_loop() { + if (!message_loop_) + message_loop_ = MessageLoop::current(); + DCHECK(message_loop_ == MessageLoop::current()); + return message_loop_; +} + + +HWND TimerManager::GetMessageHWND() { + if (!message_hwnd_) { + HINSTANCE hinst = GetModuleHandle(NULL); + + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = ::MessageWndProc; + wc.hInstance = hinst; + wc.lpszClassName = kWndClass; + RegisterClassEx(&wc); + + message_hwnd_ = CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, + hinst, 0); + DCHECK(message_hwnd_); + } + return message_hwnd_; +} + +//----------------------------------------------------------------------------- +// SimpleTimer + +SimpleTimer::SimpleTimer(TimeDelta delay, Task* task, bool repeating) + : timer_(static_cast<int>(delay.InMilliseconds()), task, repeating), + owns_task_(true) { +} + +SimpleTimer::~SimpleTimer() { + Stop(); + + if (owns_task_) + delete timer_.task(); +} + +void SimpleTimer::Start() { + DCHECK(timer_.task()); + timer_.Reset(); + MessageLoop::current()->timer_manager()->StartTimer(&timer_); +} + +void SimpleTimer::Stop() { + MessageLoop::current()->timer_manager()->StopTimer(&timer_); +} + +bool SimpleTimer::IsRunning() const { + return MessageLoop::current()->timer_manager()->IsTimerRunning(&timer_); +} + +void SimpleTimer::Reset() { + DCHECK(timer_.task()); + MessageLoop::current()->timer_manager()->ResetTimer(&timer_); +} diff --git a/base/timer.h b/base/timer.h new file mode 100644 index 0000000..39eb313 --- /dev/null +++ b/base/timer.h @@ -0,0 +1,320 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_TIMER_H_ +#define BASE_TIMER_H_ + +#include <math.h> +#include <windows.h> + +#include <queue> +#include <vector> + +#include "base/basictypes.h" +#include "base/time.h" + +// Timer/TimerManager are objects designed to help setting timers. +// Goals of TimerManager: +// - have only one Windows system timer for all app timer functionality +// - work around bugs with timers firing arbitrarily earlier than specified +// - provide the ability to run timers even if the application is in a +// windows modal app loop. + +class TimerManager; +class Task; +class MessageLoop; + +class Timer { + public: + Timer(int delay, Task* task, bool repeating); + + Task* task() const { return task_; } + void set_task(Task* task) { task_ = task; } + + int current_delay() const { + // Be careful here. Timers have 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 timers + // firing early. Implement ceiling by adding 999us prior to + // conversion to ms. + double delay = ceil((fire_time_ - Time::Now()).InMillisecondsF()); + return static_cast<int>(delay); + } + + const Time &fire_time() const { return fire_time_; } + + bool repeating() const { return repeating_; } + + // Update (or fill in) creation_time_, and calculate future fire_time_ based + // on current time plus delay_. + void Reset(); + + int id() const { return timer_id_; } + + protected: + // Protected (rather than private) so that we can access from unit tests. + + // The time when the timer should fire. + Time fire_time_; + + private: + // A sequence number for all allocated times (used to break ties when + // comparing times in the TimerManager, and assure FIFO execution sequence). + static int32 timer_id_counter_; + + // The task that is run when this timer fires. + Task* task_; + + // Timer delay in milliseconds. + int delay_; + + // A monotonically increasing timer id. Used + // for ordering two timers which have the same + // timestamp in a FIFO manner. + int timer_id_; + + // Whether or not this timer repeats. + const bool repeating_; + + // The tick count when the timer was "created". (i.e. when its current + // iteration started.) + Time creation_time_; + + DISALLOW_EVIL_CONSTRUCTORS(Timer); +}; + +class TimerComparison { + public: + bool operator() (const Timer* t1, const Timer* t2) const { + const Time& f1 = t1->fire_time(); + const Time& f2 = t2->fire_time(); + // If the two timers have the same delay, revert to using + // the timer_id to maintain FIFO ordering. + if (f1 == f2) { + // Gracefully handle wrap as we try to return (t1->id() > t2->id()); + int delta = t1->id() - t2->id(); + // Assuming the delta is smaller than 2**31, we'll always get the right + // answer (in terms of sign of delta). + return delta > 0; + } + return f1 > f2; + } +}; + +// Subclass priority_queue to provide convenient access to removal from this +// list. +// +// Terminology: The "pending" timer is the timer at the top of the queue, +// i.e. the timer whose task needs to be Run next. +class TimerPQueue : public std::priority_queue<Timer*, + std::vector<Timer*>, + TimerComparison> { + public: + // Removes |timer| from the queue. + void RemoveTimer(Timer* timer); + + // Returns true if the queue contains |timer|. + bool ContainsTimer(const Timer* timer) const; +}; + +// There is one TimerManager per thread, owned by the MessageLoop. +// Timers can either be fired directly by the MessageLoop, or by +// SetTimer and a WM_TIMER message. The advantage of the former +// is that we can make timers fire significantly faster than the 10ms +// granularity provided by SetTimer(). The advantage of SetTimer() +// is that modal message loops which don't run our MessageLoop +// code will still be able to process WM_TIMER messages. +// +// Note: TimerManager is not thread safe. You cannot set timers +// onto a thread other than your own. +class TimerManager { + public: + TimerManager(); + ~TimerManager(); + + // Create and start a new timer. |task| is owned by the caller, as is the + // timer object that is returned. + Timer* StartTimer(int delay, Task* task, bool repeating); + + // Flag indicating whether the timer manager should use the OS + // timers or not. Default is true. MessageLoops which are not reliably + // called due to nested windows message loops should set this to + // true. + bool use_native_timers() { return use_native_timers_; } + void set_use_native_timers(bool value) { use_native_timers_ = value; } + + // Starts a timer. This is a no-op if the timer is already started. + void StartTimer(Timer* timer); + + // Stop a timer. This is a no-op if the timer is already stopped. + void StopTimer(Timer* timer); + + // Reset an existing timer, which may or may not be currently in the queue of + // upcoming timers. The timer's parameters are unchanged; it simply begins + // counting down again as if it was just created. + void ResetTimer(Timer* timer); + + // Returns true if |timer| is in the queue of upcoming timers. + bool IsTimerRunning(const Timer* timer) const; + + // Run some small number of timers. + // Returns true if it runs a task, false otherwise. + bool RunSomePendingTimers(); + + // The number of milliseconds remaining until the pending timer (top of the + // pqueue) needs to be fired. Returns -1 if no timers are pending. + int GetCurrentDelay(); + + // A handler for WM_TIMER messages. + // If a task is not running currently, it runs some timer tasks (if there are + // some ready to fire), otherwise it just updates the WM_TIMER to be called + // again (hopefully when it is allowed to run a task). + int MessageWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + // Return cached copy of MessageLoop::current(). + MessageLoop* message_loop(); + +#ifdef UNIT_TEST + // For testing only, used to simulate broken early-firing WM_TIMER + // notifications by setting arbitrarily small delays in SetTimer. + void set_use_broken_delay(bool use_broken_delay) { + use_broken_delay_ = use_broken_delay; + } +#endif + + protected: + // Peek at the timer which will fire soonest. + Timer* PeekTopTimer(); + + private: + // Update our Windows WM_TIMER to match our most immediately pending timer. + void UpdateWindowsWmTimer(); + + // Retrieve the Message Window that handles WM_TIMER messages from the + // system. + HWND GetMessageHWND(); + + TimerPQueue timers_; + + bool use_broken_delay_; + + HWND message_hwnd_; + + // Flag to enable/disable use of native timers. + bool use_native_timers_; + + // A lazily cached copy of MessageLoop::current. + MessageLoop* message_loop_; + + DISALLOW_EVIL_CONSTRUCTORS(TimerManager); +}; + +// A simple wrapper for the Timer / TimerManager API. This is a helper class. +// Use OneShotTimer or RepeatingTimer instead. +class SimpleTimer { + public: + // Stops the timer. + ~SimpleTimer(); + + // Call this method to explicitly start the timer. This is a no-op if the + // timer is already running. + void Start(); + + // Call this method to explicitly stop the timer. This is a no-op if the + // timer is not running. + void Stop(); + + // Returns true if the timer is running (i.e., not stopped). + bool IsRunning() const; + + // Short-hand for calling Stop and then Start. + void Reset(); + + // Get/Set the task to be run when this timer expires. NOTE: The caller of + // set_task must be careful to ensure that the old task is properly deleted. + Task* task() const { return timer_.task(); } + void set_task(Task* task) { + timer_.set_task(task); + owns_task_ = true; + } + + // Sets the task, but marks it so it shouldn't be deleted by the SimpleTimer. + void set_unowned_task(Task* task) { + timer_.set_task(task); + owns_task_ = false; + } + + protected: + SimpleTimer(TimeDelta delay, Task* task, bool repeating); + + private: + Timer timer_; + + // Whether we need to clean up the Task* object for this Timer when + // we are deallocated. Defaults to true. + bool owns_task_; + + DISALLOW_EVIL_CONSTRUCTORS(SimpleTimer); +}; + +// A simple, one-shot timer. The task is run after the specified delay once +// the Start method is called. The task is deleted when the timer object is +// destroyed. +class OneShotTimer : public SimpleTimer { + public: + // The task must be set using set_task before calling Start. + explicit OneShotTimer(TimeDelta delay) + : SimpleTimer(delay, NULL, false) { + } + // If task is null, then it must be set using set_task before calling Start. + OneShotTimer(TimeDelta delay, Task* task) + : SimpleTimer(delay, task, false) { + } + private: + DISALLOW_EVIL_CONSTRUCTORS(OneShotTimer); +}; + +// A simple, repeating timer. The task is run at the specified interval once +// the Start method is called. The task is deleted when the timer object is +// destroyed. +class RepeatingTimer : public SimpleTimer { + public: + // The task must be set using set_task before calling Start. + explicit RepeatingTimer(TimeDelta interval) + : SimpleTimer(interval, NULL, true) { + } + // If task is null, then it must be set using set_task before calling Start. + RepeatingTimer(TimeDelta interval, Task* task) + : SimpleTimer(interval, task, true) { + } + private: + DISALLOW_EVIL_CONSTRUCTORS(RepeatingTimer); +}; + +#endif // BASE_TIMER_H_ diff --git a/base/timer_unittest.cc b/base/timer_unittest.cc new file mode 100644 index 0000000..814836b --- /dev/null +++ b/base/timer_unittest.cc @@ -0,0 +1,332 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/message_loop.h" +#include "base/task.h" +#include "base/timer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + class TimerTest : public testing::Test { + }; +}; + +// A base class timer task that sanity-checks timer functionality and counts +// the number of times it has run. Handles all message loop and memory +// management issues. +class TimerTask : public Task { + public: + // Runs all timers to completion. This returns only after all timers have + // finished firing. + static void RunTimers(); + + // Creates a new timer. If |repeating| is true, the timer will repeat 10 + // times before terminating. + // + // All timers are managed on the message loop of the thread that calls this + // function the first time. + TimerTask(int delay, bool repeating); + + virtual ~TimerTask(); + + int iterations() const { return iterations_; } + const Timer* timer() const { return timer_; } + + // Resets the timer, if it exists. + void Reset(); + + // Task + virtual void Run(); + + protected: + // Shuts down the message loop if necessary. + static void QuitMessageLoop(); + + private: + static MessageLoop* message_loop() { + static MessageLoop* loop = MessageLoop::current(); + return loop; + } + + static int timer_count_; + static bool loop_running_; + + bool timer_running_; + int delay_; + TimeTicks start_ticks_; + int iterations_; + Timer* timer_; +}; + +// static +void TimerTask::RunTimers() { + if (timer_count_ && !loop_running_) { + loop_running_ = true; + message_loop()->Run(); + } +} + +TimerTask::TimerTask(int delay, bool repeating) + : timer_running_(false), + delay_(delay), + start_ticks_(TimeTicks::Now()), + iterations_(0), + timer_(NULL) { + Reset(); // This will just set up the variables to indicate we have a + // running timer. + timer_ = message_loop()->timer_manager()->StartTimer(delay, this, repeating); +} + +TimerTask::~TimerTask() { + if (timer_) { + message_loop()->timer_manager()->StopTimer(timer_); + delete timer_; + } + if (timer_running_) { + timer_running_ = false; + if (--timer_count_ <= 0) + QuitMessageLoop(); + } +} + +void TimerTask::Reset() { + if (!timer_running_) { + timer_running_ = true; + ++timer_count_; + } + if (timer_) { + start_ticks_ = TimeTicks::Now(); + message_loop()->timer_manager()->ResetTimer(timer_); + } +} + +void TimerTask::Run() { + ++iterations_; + + // Test that we fired on or after the delay, not before. + const TimeTicks ticks = TimeTicks::Now(); + EXPECT_LE(delay_, (ticks - start_ticks_).InMilliseconds()); + // Note: Add the delay rather than using the ticks recorded. + // Repeating timers have already started ticking before + // this callback; we pretend they started *now*, then + // it might seem like they fire early, when they do not. + start_ticks_ += TimeDelta::FromMilliseconds(delay_); + + // If we're done running, shut down the message loop. + if (timer_->repeating() && (iterations_ < 10)) + return; // Iterate 10 times before terminating. + message_loop()->timer_manager()->StopTimer(timer_); + timer_running_ = false; + if (--timer_count_ <= 0) + QuitMessageLoop(); +} + +// static +void TimerTask::QuitMessageLoop() { + if (loop_running_) { + message_loop()->Quit(); + loop_running_ = false; + } +} + +int TimerTask::timer_count_ = 0; +bool TimerTask::loop_running_ = false; + +// A task that deletes itself when run. +class DeletingTask : public TimerTask { + public: + DeletingTask(int delay, bool repeating) : TimerTask(delay, repeating) { } + + // Task + virtual void Run(); +}; + +void DeletingTask::Run() { + delete this; + + // Can't call TimerTask::Run() here, we've destroyed ourselves. +} + +// A class that resets another TimerTask when run. +class ResettingTask : public TimerTask { + public: + ResettingTask(int delay, bool repeating, TimerTask* task) + : TimerTask(delay, repeating), + task_(task) { + } + + virtual void Run(); + + private: + TimerTask* task_; +}; + +void ResettingTask::Run() { + task_->Reset(); + + TimerTask::Run(); +} + +// A class that quits the message loop when run. +class QuittingTask : public TimerTask { + public: + QuittingTask(int delay, bool repeating) : TimerTask(delay, repeating) { } + + virtual void Run(); +}; + +void QuittingTask::Run() { + QuitMessageLoop(); + + TimerTask::Run(); +} + +void RunTimerTest() { + // Make sure oneshot timers work correctly. + TimerTask task1(100, false); + TimerTask::RunTimers(); + EXPECT_EQ(1, task1.iterations()); + + // Make sure repeating timers work correctly. + TimerTask task2(10, true); + TimerTask task3(100, true); + TimerTask::RunTimers(); + EXPECT_EQ(10, task2.iterations()); + EXPECT_EQ(10, task3.iterations()); +} + +TEST(TimerTest, TimerComparison) { + // Make sure TimerComparison sorts correctly. + const TimerTask task1(10, false); + const Timer* timer1 = task1.timer(); + const TimerTask task2(200, false); + const Timer* timer2 = task2.timer(); + TimerComparison comparison; + EXPECT_FALSE(comparison(timer1, timer2)); + EXPECT_TRUE(comparison(timer2, timer1)); +} + +TEST(TimerTest, TimerCase) { + RunTimerTest(); +} + +TEST(TimerTest, BrokenTimerCase) { + // Simulate faulty early-firing timers. The tasks in RunTimerTest should + // nevertheless be invoked after their specified delays, regardless of when + // WM_TIMER fires. + TimerManager* manager = MessageLoop::current()->timer_manager(); + manager->set_use_broken_delay(true); + RunTimerTest(); + manager->set_use_broken_delay(false); +} + +TEST(TimerTest, DeleteFromRun) { + // Make sure TimerManager correctly handles a Task that deletes itself when + // run. + DeletingTask* deleting_task1 = new DeletingTask(50, true); + TimerTask timer_task(150, false); + DeletingTask* deleting_task2 = new DeletingTask(250, true); + TimerTask::RunTimers(); + EXPECT_EQ(1, timer_task.iterations()); +} + +TEST(TimerTest, Reset) { + // Make sure resetting a timer after it has fired works. + TimerTask timer_task1(250, false); + TimerTask timer_task2(100, true); + ResettingTask resetting_task1(600, false, &timer_task1); + TimerTask::RunTimers(); + EXPECT_EQ(2, timer_task1.iterations()); + EXPECT_EQ(10, timer_task2.iterations()); + + // Make sure resetting a timer before it has fired works. This will reset + // two timers, then stop the message loop between when they should have + // finally fired. + TimerTask timer_task3(100, false); + TimerTask timer_task4(600, false); + ResettingTask resetting_task3(50, false, &timer_task3); + ResettingTask resetting_task4(50, false, &timer_task4); + QuittingTask quitting_task(300, false); + TimerTask::RunTimers(); + EXPECT_EQ(1, timer_task3.iterations()); + EXPECT_EQ(0, timer_task4.iterations()); +} + +TEST(TimerTest, FifoOrder) { + // Creating timers with the same timeout should + // always compare to result in FIFO ordering. + + // Derive from the timer so that we can set it's fire time. + // We have to do this, because otherwise, it's possible for + // two timers, created back to back, to have different times, + // and in that case, we aren't really testing what we want + // to test! + class MockTimer : public Timer { + public: + MockTimer(int delay) : Timer(delay, NULL, false) {} + void set_fire_time(const Time& t) { fire_time_ = t; } + }; + + class MockTimerManager : public TimerManager { + public: + // Pops the most-recent to fire timer and returns its timer id. + // Returns -1 if there are no timers in the list. + int pop() { + int rv = -1; + Timer* top = PeekTopTimer(); + if (top) { + rv = top->id(); + StopTimer(top); + delete top; + } + return rv; + } + }; + + MockTimer t1(0); + MockTimer t2(0); + t2.set_fire_time(t1.fire_time()); + TimerComparison comparison; + EXPECT_TRUE(comparison(&t2, &t1)); + + // Issue a tight loop of timers; most will have the + // same timestamp; some will not. Either way, since + // all are created with delay(0), the second timer + // must always be greater than the first. Then, pop + // all the timers and verify that it's a FIFO list. + MockTimerManager manager; + const int kNumTimers = 1024; + for (int i=0; i < kNumTimers; i++) + Timer* timer = manager.StartTimer(0, NULL, false); + + int last_id = -1; + int new_id = 0; + while((new_id = manager.pop()) > 0) + EXPECT_GT(new_id, last_id); +}
\ No newline at end of file diff --git a/base/tracked.cc b/base/tracked.cc new file mode 100644 index 0000000..59c26f6 --- /dev/null +++ b/base/tracked.cc @@ -0,0 +1,113 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/tracked.h" + +#include "base/string_util.h" +#include "base/tracked_objects.h" + +namespace tracked_objects { + +//------------------------------------------------------------------------------ +void Location::Write(bool display_filename, bool display_function_name, + std::string* output) const { + 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; + } + } +} + +//------------------------------------------------------------------------------ + +#ifndef TRACK_ALL_TASK_OBJECTS + +Tracked::Tracked() {} +Tracked::~Tracked() {} +void Tracked::SetBirthPlace(const Location& from_here) {} +bool Tracked::MissingBirthplace() const { return false; } + +#else + +Tracked::Tracked() : tracked_births_(NULL), tracked_birth_time_(Time::Now()) { + if (!ThreadData::IsActive()) + return; + SetBirthPlace(Location("NoFunctionName", "NeedToSetBirthPlace", -1)); +} + +Tracked::~Tracked() { + if (!ThreadData::IsActive() || !tracked_births_) + return; + ThreadData::current()->TallyADeath(*tracked_births_, + Time::Now() - tracked_birth_time_); +} + +void Tracked::SetBirthPlace(const Location& from_here) { + if (!ThreadData::IsActive()) + return; + if (tracked_births_) + tracked_births_->ForgetBirth(); + ThreadData* current_thread_data = ThreadData::current(); + if (!current_thread_data) + return; // Shutdown started, and this thread wasn't registered. + tracked_births_ = current_thread_data->FindLifetime(from_here); + tracked_births_->RecordBirth(); +} + +bool Tracked::MissingBirthplace() const { + return -1 == tracked_births_->location().line_number(); +} + +#endif // NDEBUG + +} // namespace tracked_objects + diff --git a/base/tracked.h b/base/tracked.h new file mode 100644 index 0000000..1b5d22a --- /dev/null +++ b/base/tracked.h @@ -0,0 +1,129 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//------------------------------------------------------------------------------ +// Tracked is the base class for all tracked objects. During construction, it +// registers the fact that an instance was created, and at destruction time, it +// records that event. The instance may be tagged with a name, which is refered +// to as its Location. The Location is a file and line number, most +// typically indicated where the object was constructed. In some cases, as the +// object's significance is refined (for example, a Task object is augmented to +// do additonal things), its Location may be redefined to that later location. + +// Tracking includes (for each instance) recording the birth thread, death +// thread, and duration of life (from construction to destruction). All this +// data is accumulated and filtered for review at about:objects. + +#ifndef BASE_TRACKED_H__ +#define BASE_TRACKED_H__ + +#include <string> + +#include "base/time.h" + +#ifndef NDEBUG +#define TRACK_ALL_TASK_OBJECTS +#endif + +namespace tracked_objects { + +//------------------------------------------------------------------------------ +// Location provides basic info where of an object was constructed, or was +// significantly brought to life. + +class 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) + : function_name_(function_name), + file_name_(file_name), + line_number_(line_number) { } + + // Provide a default constructor for easy of debugging. + Location() + : function_name_("Unknown"), + file_name_("Unknown"), + line_number_(-1) { } + + // 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_; } + + 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* const function_name_; + const char* const file_name_; + const int line_number_; +}; + + +//------------------------------------------------------------------------------ + + +class Births; + +class Tracked { + public: + Tracked(); + virtual ~Tracked(); + void SetBirthPlace(const Location& from_here); + + bool MissingBirthplace() const; + + private: + Births* tracked_births_; // At same birthplace, and same thread. + const Time tracked_birth_time_; + + DISALLOW_EVIL_CONSTRUCTORS(Tracked); +}; + +} // namespace tracked_objects + +#endif // BASE_TRACKED_H__ diff --git a/base/tracked_objects.cc b/base/tracked_objects.cc new file mode 100644 index 0000000..61c3639 --- /dev/null +++ b/base/tracked_objects.cc @@ -0,0 +1,918 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/tracked_objects.h" + +#include "base/string_util.h" + +namespace tracked_objects { + +// a TLS index to the TrackRegistry for the current thread. +// static +TLSSlot ThreadData::tls_index_ = -1; + +//------------------------------------------------------------------------------ +// Death data tallies durations when a death takes place. + +void DeathData::RecordDeath(const TimeDelta& duration) { + ++count_; + life_duration_ += duration; + int64 milliseconds = duration.InMilliseconds(); + square_duration_ += milliseconds * milliseconds; +} + +int DeathData::AverageMsDuration() const { + return static_cast<int>(life_duration_.InMilliseconds() / count_); +} + +double DeathData::StandardDeviation() const { + double average = AverageMsDuration(); + double variance = static_cast<float>(square_duration_)/count_ + - average * average; + return sqrt(variance); +} + + +void DeathData::AddDeathData(const DeathData& other) { + count_ += other.count_; + life_duration_ += other.life_duration_; + square_duration_ += other.square_duration_; +} + +void DeathData::Write(std::string* output) const { + if (!count_) + return; + if (1 == count_) + StringAppendF(output, "(1)Life in %dms ", count_, AverageMsDuration()); + else + StringAppendF(output, "(%d)Lives %dms/life ", count_, AverageMsDuration()); +} + +void DeathData::Clear() { + count_ = 0; + life_duration_ = TimeDelta(); + square_duration_ = 0; +} + +//------------------------------------------------------------------------------ + +BirthOnThread::BirthOnThread(const Location& location) + : location_(location), + birth_thread_(ThreadData::current()) { } + +//------------------------------------------------------------------------------ +Births::Births(const Location& location) + : BirthOnThread(location), + birth_count_(0) { } + +//------------------------------------------------------------------------------ +// ThreadData maintains the central data for all births and death. + +// static +ThreadData* ThreadData::first_ = NULL; +// static +Lock ThreadData::list_lock_; + +// static +ThreadData::Status ThreadData::status_ = ThreadData::UNINITIALIZED; + +ThreadData::ThreadData() : message_loop_(MessageLoop::current()) {} + +// static +ThreadData* ThreadData::current() { + if (-1 == tls_index_) + return NULL; // not yet initialized. + + ThreadData* registry = + static_cast<ThreadData*>(ThreadLocalStorage::Get(tls_index_)); + if (!registry) { + // We have to create a new registry for ThreadData. + bool too_late_to_create = false; + { + registry = new ThreadData; + AutoLock lock(list_lock_); + // Use lock to insure we have most recent status. + if (!IsActive()) { + too_late_to_create = true; + } else { + // Use lock to insert into list. + registry->next_ = first_; + first_ = registry; + } + } // Release lock. + if (too_late_to_create) { + delete registry; + registry = NULL; + } else { + ThreadLocalStorage::Set(tls_index_, registry); + } + } + return registry; +} + +// Do mininimal fixups for searching function names. +static std::string UnescapeQuery(const std::string& query) { + std::string result; + for (size_t i = 0; i < query.size(); i++) { + char next = query[i]; + if ('%' == next && i + 2 < query.size()) { + std::string hex = query.substr(i + 1, 2); + char replacement = '\0'; + // Only bother with "<", ">", and " ". + if (LowerCaseEqualsASCII(hex, "3c")) + replacement ='<'; + else if (LowerCaseEqualsASCII(hex, "3e")) + replacement = '>'; + else if (hex == "20") + replacement = ' '; + if (replacement) { + next = replacement; + i += 2; + } + } + result.push_back(next); + } + return result; +} + +// static +void ThreadData::WriteHTML(const std::string& query, std::string* output) { + if (!ThreadData::IsActive()) + return; // Not yet initialized. + + DCHECK(ThreadData::current()); + + output->append("<html><head><title>About Objects"); + std::string escaped_query = UnescapeQuery(query); + if (!escaped_query.empty()) + output->append(" - " + escaped_query); + output->append("</title></head><body><pre>"); + + DataCollector collected_data; // Gather data. + collected_data.AddListOfLivingObjects(); // Add births that are still alive. + + // Data Gathering is complete. Now to sort/process/render. + DataCollector::Collection* collection = collected_data.collection(); + + // Create filtering and sort comparison object. + Comparator comparator; + bool display_details = comparator.ParseQuery(escaped_query); + + // Filter out acceptable (matching) instances. + DataCollector::Collection match_array; + for (DataCollector::Collection::iterator it = collection->begin(); + it != collection->end(); ++it) { + if (comparator.Acceptable(*it)) + match_array.push_back(*it); + } + + comparator.Sort(&match_array); + + WriteHTMLTotalAndSubtotals(match_array, comparator, output); + + comparator.Clear(); // Delete tiebreaker_ instances. + + output->append("</pre></body></html>"); +} + +// static +void ThreadData::WriteHTMLTotalAndSubtotals( + const DataCollector::Collection& match_array, + const Comparator& comparator, + std::string* output) { + if (!match_array.size()) { + output->append("There were no tracked matches."); + } else { + // Aggregate during printing + Aggregation totals; + for (size_t i = 0; i < match_array.size(); ++i) { + totals.AddDeathSnapshot(match_array[i]); + } + output->append("Aggregate Stats: "); + totals.Write(output); + output->append("<hr><hr>"); + + Aggregation subtotals; + for (size_t i = 0; i < match_array.size(); ++i) { + if (0 == i || !comparator.Equivalent(match_array[i - 1], + match_array[i])) { + // Print group's defining characteristics. + comparator.WriteSortGrouping(match_array[i], output); + output->append("<br><br>"); + } + comparator.WriteSnapshot(match_array[i], output); + output->append("<br>"); + subtotals.AddDeathSnapshot(match_array[i]); + if (i + 1 >= match_array.size() || + !comparator.Equivalent(match_array[i], + match_array[i + 1])) { + // Print aggregate stats for the group. + output->append("<br>"); + subtotals.Write(output); + output->append("<br><hr><br>"); + subtotals.Clear(); + } + } + } +} + +Births* ThreadData::FindLifetime(const Location& location) { + if (!message_loop_) // In case message loop wasn't yet around... + message_loop_ = MessageLoop::current(); // Find it now. + + BirthMap::iterator it = birth_map_.find(location); + if (it != birth_map_.end()) + return it->second; + Births* tracker = new Births(location); + + // Lock since the map may get relocated now, and other threads sometimes + // snapshot it (but they lock before copying it). + AutoLock lock(lock_); + birth_map_[location] = tracker; + return tracker; +} + +void ThreadData::TallyADeath(const Births& lifetimes, + const TimeDelta& duration) { + if (!message_loop_) // In case message loop wasn't yet around... + message_loop_ = MessageLoop::current(); // Find it now. + + DeathMap::iterator it = death_map_.find(&lifetimes); + if (it != death_map_.end()) { + it->second.RecordDeath(duration); + return; + } + + AutoLock lock(lock_); // Lock since the map may get relocated now. + death_map_[&lifetimes].RecordDeath(duration); +} + +// static +ThreadData* ThreadData::first() { + AutoLock lock(list_lock_); + return first_; +} + +const std::string ThreadData::ThreadName() const { + if (message_loop_) + return message_loop_->thread_name(); + return "ThreadWithoutMessageLoop"; +} + +// This may be called from another thread. +void ThreadData::SnapshotBirthMap(BirthMap *output) const { + AutoLock lock(*const_cast<Lock*>(&lock_)); + for (BirthMap::const_iterator it = birth_map_.begin(); + it != birth_map_.end(); ++it) + (*output)[it->first] = it->second; +} + +// This may be called from another thread. +void ThreadData::SnapshotDeathMap(DeathMap *output) const { + AutoLock lock(*const_cast<Lock*>(&lock_)); + for (DeathMap::const_iterator it = death_map_.begin(); + it != death_map_.end(); ++it) + (*output)[it->first] = it->second; +} + +void ThreadData::RunOnAllThreads(void (*function)()) { + ThreadData* list = first(); // Get existing list. + + std::vector<MessageLoop*> message_loops; + for (ThreadData* it = list; it; it = it->next()) { + if (current() != it && it->message_loop()) + message_loops.push_back(it->message_loop()); + } + + ThreadSafeDownCounter* counter = + new ThreadSafeDownCounter(message_loops.size() + 1); // Extra one for us! + + HANDLE completion_handle = CreateEvent(NULL, false, false, NULL); + // Tell all other threads to run. + for (size_t i = 0; i < message_loops.size(); ++i) + message_loops[i]->PostTask(FROM_HERE, + new RunTheStatic(function, completion_handle, counter)); + + // Also run Task on our thread. + RunTheStatic local_task(function, completion_handle, counter); + local_task.Run(); + + WaitForSingleObject(completion_handle, INFINITE); + int ret_val = CloseHandle(completion_handle); + DCHECK(ret_val); +} + +// static +bool ThreadData::StartTracking(bool status) { +#ifndef TRACK_ALL_TASK_OBJECTS + return false; // Not compiled in. +#endif + + if (!status) { + AutoLock lock(list_lock_); + DCHECK(status_ == ACTIVE || status_ == SHUTDOWN); + status_ = SHUTDOWN; + return true; + } + TLSSlot tls_index = ThreadLocalStorage::Alloc(); + AutoLock lock(list_lock_); + DCHECK(status_ == UNINITIALIZED); + tls_index_ = tls_index; + CHECK(-1 != tls_index_); + status_ = ACTIVE; + return true; +} + +// static +bool ThreadData::IsActive() { + return status_ == ACTIVE; +} + +// static +void ThreadData::ShutdownMultiThreadTracking() { + // Using lock, guarantee that no new ThreadData instances will be created. + if (!StartTracking(false)) + return; + + RunOnAllThreads(ShutdownDisablingFurtherTracking); + + // Now the *only* threads that might change the database are the threads with + // no messages loops. They might still be adding data to their birth records, + // but since no objects are deleted on those threads, there will be no further + // access to to cross-thread data. + // We could do a cleanup on all threads except for the ones without + // MessageLoops, but we won't bother doing cleanup (destruction of data) yet. + return; +} + +// static +void ThreadData::ShutdownSingleThreadedCleanup() { + // We must be single threaded... but be careful anyway. + if (!StartTracking(false)) + return; + ThreadData* thread_data_list; + { + AutoLock lock(list_lock_); + thread_data_list = first_; + first_ = NULL; + } + + while (thread_data_list) { + ThreadData* next_thread_data = thread_data_list; + thread_data_list = thread_data_list->next(); + + for (BirthMap::iterator it = next_thread_data->birth_map_.begin(); + next_thread_data->birth_map_.end() != it; ++it) + delete it->second; // Delete the Birth Records. + next_thread_data->birth_map_.clear(); + next_thread_data->death_map_.clear(); + delete next_thread_data; // Includes all Death Records. + } + + CHECK(-1 != tls_index_); + ThreadLocalStorage::Free(tls_index_); + tls_index_ = -1; + status_ = UNINITIALIZED; +} + +// static +void ThreadData::ShutdownDisablingFurtherTracking() { + // Redundantly set status SHUTDOWN on this thread. + if (!StartTracking(false)) + return; +} + + +//------------------------------------------------------------------------------ + +ThreadData::ThreadSafeDownCounter::ThreadSafeDownCounter(size_t count) + : remaining_count_(count) { + DCHECK(remaining_count_ > 0); +} + +bool ThreadData::ThreadSafeDownCounter::LastCaller() { + { + AutoLock lock(lock_); + if (--remaining_count_) + return false; + } // Release lock, so we can delete everything in this instance. + delete this; + return true; +} + +//------------------------------------------------------------------------------ + +ThreadData::RunTheStatic::RunTheStatic(FunctionPointer function, + HANDLE completion_handle, + ThreadSafeDownCounter* counter) + : function_(function), + completion_handle_(completion_handle), + counter_(counter) { +} + +void ThreadData::RunTheStatic::Run() { + function_(); + if (counter_->LastCaller()) + SetEvent(completion_handle_); + } + + +//------------------------------------------------------------------------------ +// Individual 3-tuple of birth (place and thread) along with death thread, and +// the accumulated stats for instances (DeathData). + +Snapshot::Snapshot(const BirthOnThread& birth_on_thread, + const ThreadData& death_thread, + const DeathData& death_data) + : birth_(&birth_on_thread), + death_thread_(&death_thread), + death_data_(death_data) { +} + +Snapshot::Snapshot(const BirthOnThread& birth_on_thread, int count) + : birth_(&birth_on_thread), + death_thread_(NULL), + death_data_(DeathData(count)) { +} + +const std::string Snapshot::DeathThreadName() const { + if (death_thread_) + return death_thread_->ThreadName(); + return "Still_Alive"; +} + +void Snapshot::Write(std::string* output) const { + death_data_.Write(output); + StringAppendF(output, "%s->%s ", + birth_->birth_thread()->ThreadName().c_str(), + death_thread_->ThreadName().c_str()); + birth_->location().Write(true, true, output); +} + +void Snapshot::Add(const Snapshot& other) { + death_data_.AddDeathData(other.death_data_); +} + +//------------------------------------------------------------------------------ +// DataCollector + +DataCollector::DataCollector() { + DCHECK(ThreadData::IsActive()); + + ThreadData* my_list = ThreadData::current()->first(); + + count_of_contributing_threads_ = 0; + for (ThreadData* thread_data = my_list; + thread_data; + thread_data = thread_data->next()) { + ++count_of_contributing_threads_; + } + + // Gather data serially. A different constructor could be used to do in + // parallel, and then invoke an OnCompletion task. + for (ThreadData* thread_data = my_list; + thread_data; + thread_data = thread_data->next()) { + Append(*thread_data); + } +} + +void DataCollector::Append(const ThreadData& thread_data) { + // Get copy of data (which is done under ThreadData's lock). + ThreadData::BirthMap birth_map; + thread_data.SnapshotBirthMap(&birth_map); + ThreadData::DeathMap death_map; + thread_data.SnapshotDeathMap(&death_map); + + // Use our lock to protect our accumulation activity. + AutoLock lock(accumulation_lock_); + + DCHECK(count_of_contributing_threads_); + + for (ThreadData::DeathMap::const_iterator it = death_map.begin(); + it != death_map.end(); ++it) { + collection_.push_back(Snapshot(*it->first, thread_data, it->second)); + global_birth_count_[it->first] -= it->first->birth_count(); + } + + for (ThreadData::BirthMap::const_iterator it = birth_map.begin(); + it != birth_map.end(); ++it) { + global_birth_count_[it->second] += it->second->birth_count(); + } + + --count_of_contributing_threads_; +} + +DataCollector::Collection* DataCollector::collection() { + DCHECK(!count_of_contributing_threads_); + return &collection_; +} + +void DataCollector::AddListOfLivingObjects() { + DCHECK(!count_of_contributing_threads_); + for (BirthCount::iterator it = global_birth_count_.begin(); + it != global_birth_count_.end(); ++it) { + if (it->second > 0) + collection_.push_back(Snapshot(*it->first, it->second)); + } +} + +//------------------------------------------------------------------------------ +// Aggregation + +void Aggregation::AddDeathSnapshot(const Snapshot& snapshot) { + AddBirth(snapshot.birth()); + death_threads_[snapshot.death_thread()]++; + AddDeathData(snapshot.death_data()); +} + +void Aggregation::AddBirths(const Births& births) { + AddBirth(births); + birth_count_ += births.birth_count(); +} +void Aggregation::AddBirth(const BirthOnThread& birth) { + AddBirthPlace(birth.location()); + birth_threads_[birth.birth_thread()]++; +} + +void Aggregation::AddBirthPlace(const Location& location) { + locations_[location]++; + birth_files_[location.file_name()]++; +} + +void Aggregation::Write(std::string* output) const { + if (locations_.size() == 1) { + locations_.begin()->first.Write(true, true, output); + } else { + StringAppendF(output, "%d Locations. ", locations_.size()); + if (birth_files_.size() > 1) + StringAppendF(output, "%d Files. ", birth_files_.size()); + else + StringAppendF(output, "All born in %s. ", + birth_files_.begin()->first.c_str()); + } + + if (birth_threads_.size() > 1) + StringAppendF(output, "%d BirthingThreads. ", birth_threads_.size()); + else + StringAppendF(output, "All born on %s. ", + birth_threads_.begin()->first->ThreadName().c_str()); + + if (death_threads_.size() > 1) { + StringAppendF(output, "%d DeathThreads. ", death_threads_.size()); + } else { + if (death_threads_.begin()->first) + StringAppendF(output, "All deleted on %s. ", + death_threads_.begin()->first->ThreadName().c_str()); + else + output->append("All these objects are still alive."); + } + + if (birth_count_ > 1) + StringAppendF(output, "Births=%d ", birth_count_); + + DeathData::Write(output); +} + +void Aggregation::Clear() { + birth_count_ = 0; + birth_files_.clear(); + locations_.clear(); + birth_threads_.clear(); + DeathData::Clear(); + death_threads_.clear(); +} + +//------------------------------------------------------------------------------ +// Comparison object for sorting. + +Comparator::Comparator() + : selector_(NIL), + tiebreaker_(NULL), + combined_selectors_(0), + use_tiebreaker_for_sort_only_(false) {} + +void Comparator::Clear() { + if (tiebreaker_) { + tiebreaker_->Clear(); + delete tiebreaker_; + tiebreaker_ = NULL; + } + use_tiebreaker_for_sort_only_ = false; + selector_ = NIL; +} + +void Comparator::Sort(DataCollector::Collection* collection) const { + std::sort(collection->begin(), collection->end(), *this); +} + + +bool Comparator::operator()(const Snapshot& left, + const Snapshot& right) const { + switch (selector_) { + case BIRTH_THREAD: + if (left.birth_thread() != right.birth_thread() && + left.birth_thread()->ThreadName() != + right.birth_thread()->ThreadName()) + return left.birth_thread()->ThreadName() < + right.birth_thread()->ThreadName(); + break; + + case DEATH_THREAD: + if (left.death_thread() != right.death_thread() && + left.DeathThreadName() != + right.DeathThreadName()) { + if (!left.death_thread()) + return true; + if (!right.death_thread()) + return false; + return left.DeathThreadName() < + right.DeathThreadName(); + } + break; + + case BIRTH_FILE: + if (left.location().file_name() != right.location().file_name()) { + int comp = strcmp(left.location().file_name(), + right.location().file_name()); + if (comp) + return 0 > comp; + } + break; + + case BIRTH_FUNCTION: + if (left.location().function_name() != right.location().function_name()) { + int comp = strcmp(left.location().function_name(), + right.location().function_name()); + if (comp) + return 0 > comp; + } + break; + + case BIRTH_LINE: + if (left.location().line_number() != right.location().line_number()) + return left.location().line_number() < + right.location().line_number(); + break; + + case COUNT: + if (left.count() != right.count()) + return left.count() > right.count(); // Sort large at front of vector. + break; + + case AVERAGE_DURATION: + if (left.AverageMsDuration() != right.AverageMsDuration()) + return left.AverageMsDuration() > right.AverageMsDuration(); + break; + } + if (tiebreaker_) + return tiebreaker_->operator()(left, right); + return false; +} + +bool Comparator::Equivalent(const Snapshot& left, + const Snapshot& right) const { + switch (selector_) { + case BIRTH_THREAD: + if (left.birth_thread() != right.birth_thread() && + left.birth_thread()->ThreadName() != + right.birth_thread()->ThreadName()) + return false; + break; + + case DEATH_THREAD: + if (left.death_thread() != right.death_thread() && + left.DeathThreadName() != + right.DeathThreadName()) + return false; + break; + + case BIRTH_FILE: + if (left.location().file_name() != right.location().file_name()) { + int comp = strcmp(left.location().file_name(), + right.location().file_name()); + if (comp) + return false; + } + break; + + case BIRTH_FUNCTION: + if (left.location().function_name() != right.location().function_name()) { + int comp = strcmp(left.location().function_name(), + right.location().function_name()); + if (comp) + return false; + } + break; + + case COUNT: + if (left.count() != right.count()) + return false; + break; + + case AVERAGE_DURATION: + if (left.life_duration() != right.life_duration()) + return false; + break; + } + if (tiebreaker_ && !use_tiebreaker_for_sort_only_) + return tiebreaker_->Equivalent(left, right); + return true; +} + +bool Comparator::Acceptable(const Snapshot& sample) const { + if (required_.size()) { + switch (selector_) { + case BIRTH_THREAD: + if (sample.birth_thread()->ThreadName().find(required_) + == std::string.npos) + return false; + break; + + case DEATH_THREAD: + if (sample.DeathThreadName().find(required_) == std::string.npos) + return false; + break; + + case BIRTH_FILE: + if (!strstr(sample.location().file_name(), required_.c_str())) + return false; + break; + + case BIRTH_FUNCTION: + if (!strstr(sample.location().function_name(), required_.c_str())) + return false; + break; + } + } + if (tiebreaker_ && !use_tiebreaker_for_sort_only_) + return tiebreaker_->Acceptable(sample); + return true; +} + +void Comparator::SetTiebreaker(Selector selector, const std::string required) { + if (selector == selector_ || NIL == selector) + return; + combined_selectors_ |= selector; + if (NIL == selector_) { + selector_ = selector; + if (required.size()) + required_ = required; + return; + } + if (tiebreaker_) { + if (use_tiebreaker_for_sort_only_) { + Comparator* temp = new Comparator; + temp->tiebreaker_ = tiebreaker_; + tiebreaker_ = temp; + } + } else { + tiebreaker_ = new Comparator; + DCHECK(!use_tiebreaker_for_sort_only_); + } + tiebreaker_->SetTiebreaker(selector, required); +} + +bool Comparator::IsGroupedBy(Selector selector) const { + return 0 != (selector & combined_selectors_); +} + +void Comparator::SetSubgroupTiebreaker(Selector selector) { + if (selector == selector_ || NIL == selector) + return; + if (!tiebreaker_) { + use_tiebreaker_for_sort_only_ = true; + tiebreaker_ = new Comparator; + tiebreaker_->SetTiebreaker(selector, ""); + } else { + tiebreaker_->SetSubgroupTiebreaker(selector); + } +} + +void Comparator::ParseKeyphrase(const std::string key_phrase) { + static std::map<const std::string, Selector> key_map; + static bool initialized = false; + if (!initialized) { + initialized = true; + key_map["count"] = COUNT; + key_map["duration"] = AVERAGE_DURATION; + key_map["birth"] = BIRTH_THREAD; + key_map["death"] = DEATH_THREAD; + key_map["file"] = BIRTH_FILE; + key_map["function"] = BIRTH_FUNCTION; + key_map["line"] = BIRTH_LINE; + } + + std::string required; + size_t equal_offset = key_phrase.find('=', 0); + if (key_phrase.npos != equal_offset) + required = key_phrase.substr(equal_offset + 1, key_phrase.npos); + std::string keyword(key_phrase.substr(0, equal_offset)); + keyword = StringToLowerASCII(keyword); + if (key_map.end() == key_map.find(keyword)) + return; + SetTiebreaker(key_map[keyword], required); +} + +bool Comparator::ParseQuery(const std::string query) { + for (size_t i = 0; i < query.size();) { + size_t slash_offset = query.find('/', i); + ParseKeyphrase(query.substr(i, slash_offset - i)); + if (query.npos == slash_offset) + break; + i = slash_offset + 1; + } + + // Select subgroup ordering (if we want to display the subgroup) + SetSubgroupTiebreaker(COUNT); + SetSubgroupTiebreaker(AVERAGE_DURATION); + SetSubgroupTiebreaker(BIRTH_THREAD); + SetSubgroupTiebreaker(DEATH_THREAD); + SetSubgroupTiebreaker(BIRTH_FUNCTION); + SetSubgroupTiebreaker(BIRTH_FILE); + SetSubgroupTiebreaker(BIRTH_LINE); + + return true; +} + +bool Comparator::WriteSortGrouping(const Snapshot& sample, + std::string* output) const { + bool wrote_data = false; + switch (selector_) { + case NIL: + break; + + case BIRTH_THREAD: + StringAppendF(output, "All new on %s ", + sample.birth_thread()->ThreadName().c_str()); + wrote_data = true; + break; + + case DEATH_THREAD: + if (sample.death_thread()) + StringAppendF(output, "All deleted on %s ", + sample.DeathThreadName().c_str()); + else + output->append("All still alive "); + wrote_data = true; + break; + + case BIRTH_FILE: + StringAppendF(output, "All born in %s ", + sample.location().file_name()); + break; + + case BIRTH_FUNCTION: + output->append("All born in "); + sample.location().WriteFunctionName(output); + output->push_back(' '); + break; + } + if (tiebreaker_ && !use_tiebreaker_for_sort_only_) { + wrote_data |= tiebreaker_->WriteSortGrouping(sample, output); + } + return wrote_data; +} + +void Comparator::WriteSnapshot(const Snapshot& sample, + std::string* output) const { + sample.death_data().Write(output); + if (!(combined_selectors_ & BIRTH_THREAD) || + !(combined_selectors_ & DEATH_THREAD)) + StringAppendF(output, "%s->%s ", + (combined_selectors_ & BIRTH_THREAD) ? "*" : + sample.birth().birth_thread()->ThreadName().c_str(), + (combined_selectors_ & DEATH_THREAD) ? "*" : + sample.DeathThreadName().c_str()); + sample.birth().location().Write(!(combined_selectors_ & BIRTH_FILE), + !(combined_selectors_ & BIRTH_FUNCTION), + output); +} + +} // namespace tracked_objects diff --git a/base/tracked_objects.h b/base/tracked_objects.h new file mode 100644 index 0000000..23638e1 --- /dev/null +++ b/base/tracked_objects.h @@ -0,0 +1,509 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_TRACKED_OBJECTS_H_ +#define BASE_TRACKED_OBJECTS_H_ + +//------------------------------------------------------------------------------ +#include <map> +#include <string> +#include <vector> + +#include "base/lock.h" +#include "base/message_loop.h" +#include "base/thread_local_storage.h" +#include "base/tracked.h" + + +namespace tracked_objects { + +//------------------------------------------------------------------------------ +// For a specific thread, and a specific birth place, the collection of all +// death info (with tallies for each death thread, to prevent access conflicts). +class ThreadData; +class BirthOnThread { + public: + explicit BirthOnThread(const Location& location); + + const Location location() const { return location_; } + const ThreadData* birth_thread() const { return birth_thread_; } + + private: + // File/lineno of birth. This defines the essence of the type, as the context + // of the birth (construction) often tell what the item is for. This field + // is const, and hence safe to access from any thread. + const Location location_; + + // The thread that records births into this object. Only this thread is + // allowed to access birth_count_ (which changes over time). + const ThreadData* birth_thread_; // The thread this birth took place on. + + DISALLOW_EVIL_CONSTRUCTORS(BirthOnThread); +}; + +//------------------------------------------------------------------------------ +// A class for accumulating counts of births (without bothering with a map<>). + +class Births: public BirthOnThread { + public: + explicit Births(const Location& location); + + int birth_count() const { return birth_count_; } + + // When we have a birth we update the count for this BirhPLace. + void RecordBirth() { ++birth_count_; } + + // When a birthplace is changed (updated), we need to decrement the counter + // for the old instance. + void ForgetBirth() { --birth_count_; } // We corrected a birth place. + + private: + // The number of births on this thread for our location_. + int birth_count_; + + DISALLOW_EVIL_CONSTRUCTORS(Births); +}; + +//------------------------------------------------------------------------------ +// Basic info summarizing multiple destructions of an object with a single +// birthplace (fixed Location). Used both on specific threads, and also used +// in snapshots when integrating assembled data. + +class DeathData { + public: + // Default initializer. + DeathData() : count_(0), square_duration_(0) {} + + // When deaths have not yet taken place, and we gather data from all the + // threads, we create DeathData stats that tally the number of births without + // a corrosponding death. + explicit DeathData(int count) : count_(count), square_duration_(0) {} + + void RecordDeath(const TimeDelta& duration); + + // Metrics accessors. + int count() const { return count_; } + TimeDelta life_duration() const { return life_duration_; } + int64 square_duration() const { return square_duration_; } + int AverageMsDuration() const; + double StandardDeviation() const; + + // Accumulate metrics from other into this. + void AddDeathData(const DeathData& other); + + // Simple print of internal state. + void Write(std::string* output) const; + + void Clear(); + + private: + int count_; // Number of destructions. + TimeDelta life_duration_; // Sum of all lifetime durations. + int64 square_duration_; // Sum of squares in milliseconds. +}; + +//------------------------------------------------------------------------------ +// A temporary collection of data that can be sorted and summarized. It is +// gathered (carefully) from many threads. Instances are held in arrays and +// processed, filtered, and rendered. +// The source of this data was collected on many threads, and is asynchronously +// changing. The data in this instance is not asynchronously changing. + +class Snapshot { + public: + // When snapshotting a full life cycle set (birth-to-death), use this: + Snapshot(const BirthOnThread& birth_on_thread, const ThreadData& death_thread, + const DeathData& death_data); + + // When snapshotting a birth, with no death yet, use this: + Snapshot(const BirthOnThread& birth_on_thread, int count); + + + const ThreadData* birth_thread() const { return birth_->birth_thread(); } + const Location location() const { return birth_->location(); } + const BirthOnThread& birth() const { return *birth_; } + const ThreadData* death_thread() const {return death_thread_; } + const DeathData& death_data() const { return death_data_; } + const std::string DeathThreadName() const; + + int count() const { return death_data_.count(); } + TimeDelta life_duration() const { return death_data_.life_duration(); } + int64 square_duration() const { return death_data_.square_duration(); } + int AverageMsDuration() const { return death_data_.AverageMsDuration(); } + + void Snapshot::Write(std::string* output) const; + + void Add(const Snapshot& other); + + private: + const BirthOnThread* birth_; // Includes Location and birth_thread. + const ThreadData* death_thread_; + DeathData death_data_; +}; +//------------------------------------------------------------------------------ +// DataCollector is a container class for Snapshot and BirthOnThread count +// items. It protects the gathering under locks, so that it could be called via +// Posttask on any threads, such as all the target threads in parallel. + +class DataCollector { + public: + typedef std::vector<const Snapshot> Collection; + + // Construct with a list of how many threads should contribute. This helps us + // determine (in the async case) when we are done with all contributions. + DataCollector(); + + // Add all stats from the indicated thread into our arrays. This function is + // mutex protected, and *could* be called from any threads (although current + // implementation serialized calls to Append). + void Append(const ThreadData& thread_data); + + // After the accumulation phase, the following access is to process data. + Collection* collection(); + + // After collection of death data is complete, we can add entries for all the + // remaining living objects. + void AddListOfLivingObjects(); + + private: + // This instance may be provided to several threads to contribute data. The + // following counter tracks how many more threads will contribute. When it is + // zero, then all asynchronous contributions are complete, and locked access + // is no longer needed. + int count_of_contributing_threads_; + + // The array that we collect data into. + Collection collection_; + + // The total number of births recorded at each location for which we have not + // seen a death count. + typedef std::map<const BirthOnThread*, int> BirthCount; + BirthCount global_birth_count_; + + Lock accumulation_lock_; // Protects access during accumulation phase. + + DISALLOW_EVIL_CONSTRUCTORS(DataCollector); +}; + +//------------------------------------------------------------------------------ +// Aggregation contains summaries (totals and subtotals) of groups of Snapshot +// instances to provide printing of these collections on a single line. + +class Aggregation: public DeathData { + public: + Aggregation() : birth_count_(0) {} + + void AddDeathSnapshot(const Snapshot& snapshot); + void AddBirths(const Births& births); + void AddBirth(const BirthOnThread& birth); + void AddBirthPlace(const Location& location); + void Write(std::string* output) const; + void Clear(); + + private: + int birth_count_; + std::map<std::string, int> birth_files_; + std::map<Location, int> locations_; + std::map<const ThreadData*, int> birth_threads_; + DeathData death_data_; + std::map<const ThreadData*, int> death_threads_; + + DISALLOW_EVIL_CONSTRUCTORS(Aggregation); +}; + +//------------------------------------------------------------------------------ +// Comparator does the comparison of Snapshot instances. It is +// used to order the instances in a vector. It orders them into groups (for +// aggregation), and can also order instances within the groups (for detailed +// rendering of the instances). + +class Comparator { + public: + enum Selector { + NIL = 0, + BIRTH_THREAD = 1, + DEATH_THREAD = 2, + BIRTH_FILE = 4, + BIRTH_FUNCTION = 8, + BIRTH_LINE = 16, + COUNT = 32, + AVERAGE_DURATION = 64, + TOTAL_DURATION = 128, + }; + + explicit Comparator(); + + // Reset the comparator to a NIL selector. Reset() and recursively delete any + // tiebreaker_ entries. NOTE: We can't use a standard destructor, because + // the sort algorithm makes copies of this object, and then deletes them, + // which would cause problems (either we'd make expensive deep copies, or we'd + // do more thna one delete on a tiebreaker_. + void Clear(); + + // The less() operator for sorting the array via std::sort(). + bool operator()(const Snapshot& left, const Snapshot& right) const; + + void Sort(DataCollector::Collection* collection) const; + + // Check to see if the items are sort equivalents (should be aggregated). + bool Equivalent(const Snapshot& left, const Snapshot& right) const; + + // Check to see if all required fields are present in the given sample. + bool Acceptable(const Snapshot& sample) const; + + // A comparator can be refined by specifying what to do if the selected basis + // for comparison is insufficient to establish an ordering. This call adds + // the indicated attribute as the new "least significant" basis of comparison. + void SetTiebreaker(Selector selector, const std::string required); + + // Indicate if this instance is set up to sort by the given Selector, thereby + // putting that information in the SortGrouping, so it is not needed in each + // printed line. + bool IsGroupedBy(Selector selector) const; + + // Using the tiebreakers as set above, we mostly get an ordering, which + // equivalent groups. If those groups are displayed (rather than just being + // aggregated, then the following is used to order them (within the group). + void SetSubgroupTiebreaker(Selector selector); + + // Translate a keyword and restriction in URL path to a selector for sorting. + void ParseKeyphrase(const std::string key_phrase); + + // Parse a query in an about:objects URL to decide on sort ordering. + bool ParseQuery(const std::string query); + + // Output a header line that can be used to indicated what items will be + // collected in the group. It lists all (potentially) tested attributes and + // their values (in the sample item). + bool WriteSortGrouping(const Snapshot& sample, std::string* output) const; + + // Output a sample, with SortGroup details not displayed. + void WriteSnapshot(const Snapshot& sample, std::string* output) const; + + private: + // The selector directs this instance to compare based on the specified + // members of the tested elements. + enum Selector selector_; + + // For filtering into acceptable and unacceptable snapshot instance, the + // following is required to be a substring of the selector_ field. + std::string required_; + + // If this instance can't decide on an ordering, we can consult a tie-breaker + // which may have a different basis of comparison. + Comparator* tiebreaker_; + + // We or together all the selectors we sort on (not counting sub-group + // selectors), so that we can tell if we've decided to group on any given + // criteria. + int combined_selectors_; + + // Some tiebreakrs are for subgroup ordering, and not for basic ordering (in + // preparation for aggregation). The subgroup tiebreakers are not consulted + // when deciding if two items are in equivalent groups. This flag tells us + // to ignore the tiebreaker when doing Equivalent() testing. + bool use_tiebreaker_for_sort_only_; +}; + + +//------------------------------------------------------------------------------ +// For each thread, we have a ThreadData that stores all tracking info generated +// on this thread. This prevents the need for locking as data accumulates. + +class ThreadData { + public: + typedef std::map<Location, Births*> BirthMap; + typedef std::map<const Births*, DeathData> DeathMap; + + ThreadData(); + + // Using Thread Local Store, find the current instance for collecting data. + // If an instance does not exist, construct one (and remember it for use on + // this thread. + // If shutdown has already started, and we don't yet have an instance, then + // return null. + static ThreadData* current(); + + // For a given about:objects URL, develop resulting HTML, and append to + // output. + static void WriteHTML(const std::string& query, std::string* output); + + // For a given accumulated array of results, use the comparator to sort and + // subtotal, writing the results to the output. + static void WriteHTMLTotalAndSubtotals( + const DataCollector::Collection& match_array, + const Comparator& comparator, std::string* output); + + // In this thread's data, find a place to record a new birth. + Births* FindLifetime(const Location& location); + + // Find a place to record a death on this thread. + void TallyADeath(const Births& lifetimes, const TimeDelta& duration); + + // (Thread safe) Get start of list of instances. + static ThreadData* first(); + // Iterate through the null terminated list of instances. + ThreadData* next() const { return next_; } + + MessageLoop* message_loop() const { return message_loop_; } + const std::string ThreadName() const; + + // Using our lock, make a copy of the specified maps. These calls may arrive + // from non-local threads. + void SnapshotBirthMap(BirthMap *output) const; + void SnapshotDeathMap(DeathMap *output) const; + + static void RunOnAllThreads(void (*Func)()); + + // Set internal status_ to either become ACTIVE, or later, to be SHUTDOWN, + // based on argument being true or false respectively. + // IF tracking is not compiled in, this function will return false. + static bool StartTracking(bool status); + static bool IsActive(); + + // WARNING: ONLY call this function when all MessageLoops are still intact for + // all registered threads. IF you call it later, you will crash. + // Note: You don't need to call it at all, and you can wait till you are + // single threaded (again) to do the cleanup via + // ShutdownSingleThreadedCleanup(). + // Start the teardown (shutdown) process in a multi-thread mode by disabling + // further additions to thread database on all threads. First it makes a + // local (locked) change to prevent any more threads from registering. Then + // it Posts a Task to all registered threads to be sure they are aware that no + // more accumulation can take place. + static void ShutdownMultiThreadTracking(); + + // WARNING: ONLY call this function when you are running single threaded + // (again) and all message loops and threads have terminated. Until that + // point some threads may still attempt to write into our data structures. + // Delete recursively all data structures, starting with the list of + // ThreadData instances. + static void ShutdownSingleThreadedCleanup(); + + private: + // Current allowable states of the tracking system. The states always + // proceed towards SHUTDOWN, and never go backwards. + enum Status { + UNINITIALIZED, + ACTIVE, + SHUTDOWN, + }; + + // A class used to count down which is accessed by several threads. This is + // used to make sure RunOnAllThreads() actually runs a task on the expected + // count of threads. + class ThreadSafeDownCounter { + public: + // Constructor sets the count, once and for all. + explicit ThreadSafeDownCounter(size_t count); + + // Decrement the count, and return true if we hit zero. Also delete this + // instance automatically when we hit zero. + bool LastCaller(); + + private: + size_t remaining_count_; + Lock lock_; // protect access to remaining_count_. + }; + + // A Task class that runs a static method supplied, and checks to see if this + // is the last tasks instance (on last thread) that will run the method. + // IF this is the last run, then the supplied event is signalled. + class RunTheStatic : public Task { + public: + typedef void (*FunctionPointer)(); + RunTheStatic(FunctionPointer function, + HANDLE completion_handle, + ThreadSafeDownCounter* counter); + // Run the supplied static method, and optionally set the event. + void Run(); + + private: + FunctionPointer function_; + HANDLE completion_handle_; + // Make sure enough tasks are called before completion is signaled. + ThreadSafeDownCounter* counter_; + + DISALLOW_EVIL_CONSTRUCTORS(RunTheStatic); + }; + + // Each registered thread is called to set status_ to SHUTDOWN. + // This is done redundantly on every registered thread because it is not + // protected by a mutex. Running on all threads guarantees we get the + // notification into the memory cache of all possible threads. + static void ShutdownDisablingFurtherTracking(); + + // We use thread local store to identify which ThreadData to interact with. + static TLSSlot tls_index_ ; + + // Link to the most recently created instance (starts a null terminated list). + static ThreadData* first_; + // Protection for access to first_. + static Lock list_lock_; + + + // We set status_ to SHUTDOWN when we shut down the tracking service. This + // setting is redundantly established by all participating + // threads so that we are *guaranteed* (without locking) that all threads + // can "see" the status and avoid additional calls into the service. + static Status status_; + + // Link to next instance (null terminated list). Used to globally track all + // registered instances (corresponds to all registered threads where we keep + // data). + ThreadData* next_; + + // The message loop where tasks needing to access this instance's private data + // should be directed. Since some threads have no message loop, some + // instances have data that can't be (safely) modified externally. + MessageLoop* message_loop_; + + // A map used on each thread to keep track of Births on this thread. + // This map should only be accessed on the thread it was constructed on. + // When a snapshot is needed, this structure can be locked in place for the + // duration of the snapshotting activity. + BirthMap birth_map_; + + // Similar to birth_map_, this records informations about death of tracked + // instances (i.e., when a tracked instance was destroyed on this thread). + DeathMap death_map_; + + // Lock to protect *some* access to BirthMap and DeathMap. We only use + // locking protection when we are growing the maps, or using an iterator. We + // only do writes to members from this thread, so the updates of values are + // atomic. Folks can read from other threads, and get (via races) new or old + // data, but that is considered acceptable errors (mis-information). + Lock lock_; + + DISALLOW_EVIL_CONSTRUCTORS(ThreadData); +}; + +} // namespace tracked_objects + +#endif // BASE_TRACKED_OBJECTS_H_ diff --git a/base/tracked_objects_test.cc b/base/tracked_objects_test.cc new file mode 100644 index 0000000..be30745 --- /dev/null +++ b/base/tracked_objects_test.cc @@ -0,0 +1,122 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Test of classes in the tracked_objects.h classes. + +#include "base/tracked_objects.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tracked_objects { + +class TrackedObjectsTest : public testing::Test { +}; + +TEST(TrackedObjectsTest, MinimalStartupShutdown) { + // Minimal test doesn't even create any tasks. + if (!ThreadData::StartTracking(true)) + return; + + EXPECT_FALSE(ThreadData::first()); // No activity even on this thread. + ThreadData* data = ThreadData::current(); + EXPECT_TRUE(ThreadData::first()); // Now class was constructed. + EXPECT_TRUE(data); + EXPECT_TRUE(!data->next()); + EXPECT_EQ(data, ThreadData::current()); + ThreadData::BirthMap birth_map; + data->SnapshotBirthMap(&birth_map); + EXPECT_EQ(0u, birth_map.size()); + ThreadData::DeathMap death_map; + data->SnapshotDeathMap(&death_map); + EXPECT_EQ(0u, death_map.size()); + ThreadData::ShutdownSingleThreadedCleanup(); + + // Do it again, just to be sure we reset state completely. + ThreadData::StartTracking(true); + EXPECT_FALSE(ThreadData::first()); // No activity even on this thread. + data = ThreadData::current(); + EXPECT_TRUE(ThreadData::first()); // Now class was constructed. + EXPECT_TRUE(data); + EXPECT_TRUE(!data->next()); + EXPECT_EQ(data, ThreadData::current()); + birth_map.clear(); + data->SnapshotBirthMap(&birth_map); + EXPECT_EQ(0u, birth_map.size()); + death_map.clear(); + data->SnapshotDeathMap(&death_map); + EXPECT_EQ(0u, death_map.size()); + ThreadData::ShutdownSingleThreadedCleanup(); +} + +class NoopTask : public Task { + public: + void Run() {} +}; + +TEST(TrackedObjectsTest, TinyStartupShutdown) { + if (!ThreadData::StartTracking(true)) + return; + + // Instigate tracking on a single task, or our thread. + NoopTask task; + + const ThreadData* data = ThreadData::first(); + EXPECT_TRUE(data); + EXPECT_TRUE(!data->next()); + EXPECT_EQ(data, ThreadData::current()); + ThreadData::BirthMap birth_map; + data->SnapshotBirthMap(&birth_map); + EXPECT_EQ(1u, birth_map.size()); // 1 birth location. + EXPECT_EQ(1, birth_map.begin()->second->birth_count()); // 1 birth. + ThreadData::DeathMap death_map; + data->SnapshotDeathMap(&death_map); + EXPECT_EQ(0u, death_map.size()); // No deaths. + + + // Now instigate a birth, and a death. + delete new NoopTask; + + birth_map.clear(); + data->SnapshotBirthMap(&birth_map); + EXPECT_EQ(1u, birth_map.size()); // 1 birth location. + EXPECT_EQ(2, birth_map.begin()->second->birth_count()); // 2 births. + death_map.clear(); + data->SnapshotDeathMap(&death_map); + EXPECT_EQ(1u, death_map.size()); // 1 location. + EXPECT_EQ(1, death_map.begin()->second.count()); // 1 death. + + // The births were at the same location as the one known death. + EXPECT_EQ(birth_map.begin()->second, death_map.begin()->first); + + ThreadData::ShutdownSingleThreadedCleanup(); +} + + +} // namespace tracked_objects + diff --git a/base/tuple.h b/base/tuple.h new file mode 100644 index 0000000..6222a41 --- /dev/null +++ b/base/tuple.h @@ -0,0 +1,687 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_TUPLE_H__ +#define BASE_TUPLE_H__ + +// Traits ---------------------------------------------------------------------- +// +// A simple traits class for tuple arguments. +// +// ValueType: the bare, nonref version of a type (same as the type for nonrefs). +// RefType: the ref version of a type (same as the type for refs). +// ParamType: what type to pass to functions (refs should not be constified). + +template <class P> +struct TupleTraits { + typedef P ValueType; + typedef P& RefType; + typedef const P& ParamType; +}; + +template <class P> +struct TupleTraits<P&> { + typedef P ValueType; + typedef P& RefType; + typedef P& ParamType; +}; + +// Tuple ----------------------------------------------------------------------- +// +// This set of classes is useful for bundling 0 or more heterogeneous data types +// into a single variable. The advantage of this is that it greatly simplifies +// function objects that need to take an arbitrary number of parameters; see +// RunnableMethod and IPC::MessageWithTuple. +// +// Tuple0 is supplied to act as a 'void' type. It can be used, for example, when +// dispatching to a function that accepts no arguments (see the Dispatchers +// below). +// Tuple1<A> is rarely useful. One such use is when A is non-const ref that you +// want filled by the dispatchee, and the tuple is merely a container for that +// output (a "tier"). See MakeRefTuple and its usages. + +struct Tuple0 { + typedef Tuple0 ValueTuple; + typedef Tuple0 RefTuple; +}; + +template <class A> +struct Tuple1 { + public: + typedef A TypeA; + typedef Tuple1<typename TupleTraits<A>::ValueType> ValueTuple; + typedef Tuple1<typename TupleTraits<A>::RefType> RefTuple; + + Tuple1() {} + explicit Tuple1(typename TupleTraits<A>::ParamType a) : a(a) {} + + A a; +}; + +template <class A, class B> +struct Tuple2 { + public: + typedef A TypeA; + typedef B TypeB; + typedef Tuple2<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType> ValueTuple; + typedef Tuple2<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType> RefTuple; + + Tuple2() {} + Tuple2(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b) + : a(a), b(b) { + } + + A a; + B b; +}; + +template <class A, class B, class C> +struct Tuple3 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef Tuple3<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType> ValueTuple; + typedef Tuple3<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType> RefTuple; + + Tuple3() {} + Tuple3(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c) + : a(a), b(b), c(c){ + } + + A a; + B b; + C c; +}; + +template <class A, class B, class C, class D> +struct Tuple4 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef Tuple4<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType, + typename TupleTraits<D>::ValueType> ValueTuple; + typedef Tuple4<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType, + typename TupleTraits<D>::RefType> RefTuple; + + Tuple4() {} + Tuple4(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c, + typename TupleTraits<D>::ParamType d) + : a(a), b(b), c(c), d(d) { + } + + A a; + B b; + C c; + D d; +}; + +template <class A, class B, class C, class D, class E> +struct Tuple5 { +public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + typedef Tuple5<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType, + typename TupleTraits<D>::ValueType, + typename TupleTraits<E>::ValueType> ValueTuple; + typedef Tuple5<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType, + typename TupleTraits<D>::RefType, + typename TupleTraits<E>::RefType> RefTuple; + + Tuple5() {} + Tuple5(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c, + typename TupleTraits<D>::ParamType d, + typename TupleTraits<E>::ParamType e) + : a(a), b(b), c(c), d(d), e(e) { + } + + A a; + B b; + C c; + D d; + E e; +}; + +// Tuple creators ------------------------------------------------------------- +// +// Helper functions for constructing tuples while inferring the template +// argument types. + +inline Tuple0 MakeTuple() { + return Tuple0(); +} + +template <class A> +inline Tuple1<A> MakeTuple(const A& a) { + return Tuple1<A>(a); +} + +template <class A, class B> +inline Tuple2<A, B> MakeTuple(const A& a, const B& b) { + return Tuple2<A, B>(a, b); +} + +template <class A, class B, class C> +inline Tuple3<A, B, C> MakeTuple(const A& a, const B& b, const C& c) { + return Tuple3<A, B, C>(a, b, c); +} + +template <class A, class B, class C, class D> +inline Tuple4<A, B, C, D> MakeTuple(const A& a, const B& b, const C& c, + const D& d) { + return Tuple4<A, B, C, D>(a, b, c, d); +} + +template <class A, class B, class C, class D, class E> +inline Tuple5<A, B, C, D, E> MakeTuple(const A& a, const B& b, const C& c, + const D& d, const E& e) { + return Tuple5<A, B, C, D, E>(a, b, c, d, e); +} + +// The following set of helpers make what Boost refers to as "Tiers" - a tuple +// of references. + +template <class A> +inline Tuple1<A&> MakeRefTuple(A& a) { + return Tuple1<A&>(a); +} + +template <class A, class B> +inline Tuple2<A&, B&> MakeRefTuple(A& a, B& b) { + return Tuple2<A&, B&>(a, b); +} + +template <class A, class B, class C> +inline Tuple3<A&, B&, C&> MakeRefTuple(A& a, B& b, C& c) { + return Tuple3<A&, B&, C&>(a, b, c); +} + +template <class A, class B, class C, class D> +inline Tuple4<A&, B&, C&, D&> MakeRefTuple(A& a, B& b, C& c, D& d) { + return Tuple4<A&, B&, C&, D&>(a, b, c, d); +} + +template <class A, class B, class C, class D, class E> +inline Tuple5<A&, B&, C&, D&, E&> MakeRefTuple(A& a, B& b, C& c, D& d, E& e) { + return Tuple5<A&, B&, C&, D&, E&>(a, b, c, d, e); +} + +// Dispatchers ---------------------------------------------------------------- +// +// Helper functions that call the given method on an object, with the unpacked +// tuple arguments. Notice that they all have the same number of arguments, +// so you need only write: +// DispatchToMethod(object, &Object::method, args); +// This is very useful for templated dispatchers, since they don't need to know +// what type |args| is. + +// Non-Static Dispatchers with no out params. + +template <class ObjT, class Method> +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple0& arg) { + (obj->*method)(); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, Method method, const A& arg) { + (obj->*method)(arg); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple1<A>& arg) { + (obj->*method)(arg.a); +} + +template<class ObjT, class Method, class A, class B> +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple2<A, B>& arg) { + (obj->*method)(arg.a, arg.b); +} + +template<class ObjT, class Method, class A, class B, class C> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<A, B, C>& arg) { + (obj->*method)(arg.a, arg.b, arg.c); +} + +template<class ObjT, class Method, class A, class B, class C, class D> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<A, B, C, D>& arg) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<A, B, C, D, E>& arg) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e); +} + +// Static Dispatchers with no out params. + +template <class Function> +inline void DispatchToFunction(Function function, const Tuple0& arg) { + (*function)(); +} + +template <class Function, class A> +inline void DispatchToFunction(Function function, const A& arg) { + (*function)(arg); +} + +template <class Function, class A> +inline void DispatchToFunction(Function function, const Tuple1<A>& arg) { + (*function)(arg.a); +} + +template<class Function, class A, class B> +inline void DispatchToFunction(Function function, const Tuple2<A, B>& arg) { + (*function)(arg.a, arg.b); +} + +template<class Function, class A, class B, class C> +inline void DispatchToFunction(Function function, const Tuple3<A, B, C>& arg) { + (*function)(arg.a, arg.b, arg.c); +} + +template<class Function, class A, class B, class C, class D> +inline void DispatchToFunction(Function function, + const Tuple4<A, B, C, D>& arg) { + (*function)(arg.a, arg.b, arg.c, arg.d); +} + +template<class Function, class A, class B, class C, class D, class E> +inline void DispatchToFunction(Function function, + const Tuple5<A, B, C, D, E>& arg) { + (*function)(arg.a, arg.b, arg.c, arg.d, arg.e); +} + +// Dispatchers with 0 out param (as a Tuple0). + +template <class ObjT, class Method> +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple0& arg, Tuple0*) { + (obj->*method)(); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, Method method, const A& arg, Tuple0*) { + (obj->*method)(arg); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple1<A>& arg, Tuple0*) { + (obj->*method)(arg.a); +} + +template<class ObjT, class Method, class A, class B> +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple2<A, B>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b); +} + +template<class ObjT, class Method, class A, class B, class C> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<A, B, C>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b, arg.c); +} + +template<class ObjT, class Method, class A, class B, class C, class D> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<A, B, C, D>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<A, B, C, D, E>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e); +} + +// Dispatchers with 1 out param. + +template<class ObjT, class Method, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple1<OutA>* out) { + (obj->*method)(&out->a); +} + +template<class ObjT, class Method, class InA, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple1<OutA>* out) { + (obj->*method)(in, &out->a); +} + +template<class ObjT, class Method, class InA, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, &out->a); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, &out->a); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, &out->a); +} + +// Dispatchers with 2 out params. + +template<class ObjT, class Method, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(&out->a, &out->b); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, &out->a, &out->b); +} + +// Dispatchers with 3 out params. + +template<class ObjT, class Method, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(&out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, &out->a, &out->b, &out->c); +} + +// Dispatchers with 4 out params. + +template<class ObjT, class Method, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(&out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, + &out->a, &out->b, &out->c, &out->d); +} + +// Dispatchers with 5 out params. + +template<class ObjT, class Method, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple0& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(&out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b, &out->c, &out->d, + &out->e); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, + &out->a, &out->b, &out->c, &out->d, &out->e); +} + +#endif // BASE_TUPLE_H__ diff --git a/base/values.cc b/base/values.cc new file mode 100644 index 0000000..1cad628 --- /dev/null +++ b/base/values.cc @@ -0,0 +1,583 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/logging.h" +#include "base/values.h" + +///////////////////// Value //////////////////// + +Value::~Value() { +} + +// static +Value* Value::CreateNullValue() { + return new Value(TYPE_NULL); +} + +// static +Value* Value::CreateBooleanValue(bool in_value) { + return new FundamentalValue(in_value); +} + +// static +Value* Value::CreateIntegerValue(int in_value) { + return new FundamentalValue(in_value); +} + +// static +Value* Value::CreateRealValue(double in_value) { + return new FundamentalValue(in_value); +} + +// static +Value* Value::CreateStringValue(const std::wstring& in_value) { + return new StringValue(in_value); +} + +// static +BinaryValue* Value::CreateBinaryValue(char* buffer, size_t size) { + return BinaryValue::Create(buffer, size); +} + +bool Value::GetAsBoolean(bool* in_value) const { + return false; +} + +bool Value::GetAsInteger(int* in_value) const { + return false; +} + +bool Value::GetAsReal(double* in_value) const { + return false; +} + +bool Value::GetAsString(std::wstring* in_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); +} + +///////////////////// FundamentalValue //////////////////// + +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::GetAsReal(double* out_value) const { + if (out_value && IsType(TYPE_REAL)) + *out_value = real_value_; + return (IsType(TYPE_REAL)); +} + +Value* FundamentalValue::DeepCopy() const { + switch (GetType()) { + case TYPE_BOOLEAN: + return CreateBooleanValue(boolean_value_); + + case TYPE_INTEGER: + return CreateIntegerValue(integer_value_); + + case TYPE_REAL: + return CreateRealValue(real_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_REAL: { + double lhs, rhs; + return GetAsReal(&lhs) && other->GetAsReal(&rhs) && lhs == rhs; + } + default: + NOTREACHED(); + return false; + } +} + +///////////////////// StringValue //////////////////// + +StringValue::~StringValue() { +} + +bool StringValue::GetAsString(std::wstring* out_value) const { + if (out_value) + *out_value = value_; + return true; +} + +Value* StringValue::DeepCopy() const { + return CreateStringValue(value_); +} + +bool StringValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + std::wstring lhs, rhs; + return GetAsString(&lhs) && other->GetAsString(&rhs) && lhs == rhs; +} + +///////////////////// BinaryValue //////////////////// + +//static +BinaryValue* BinaryValue::Create(char* buffer, size_t size) { + if (!buffer) + return NULL; + + return new BinaryValue(buffer, size); +} + +//static +BinaryValue* BinaryValue::CreateWithCopiedBuffer(char* buffer, size_t size) { + if (!buffer) + return NULL; + + char* buffer_copy = new char[size]; + memcpy_s(buffer_copy, size, buffer, size); + return new BinaryValue(buffer_copy, size); +} + + +BinaryValue::BinaryValue(char* buffer, size_t size) + : Value(TYPE_BINARY), + buffer_(buffer), + size_(size) { + DCHECK(buffer_); +} + +BinaryValue::~BinaryValue() { + DCHECK(buffer_); + if (buffer_) + delete[] buffer_; +} + +Value* BinaryValue::DeepCopy() const { + return CreateWithCopiedBuffer(buffer_, size_); +} + +bool BinaryValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + const BinaryValue* other_binary = static_cast<const BinaryValue*>(other); + if (other_binary->size_ != size_) + return false; + return !memcmp(buffer_, other_binary->buffer_, size_); +} + +///////////////////// DictionaryValue //////////////////// + +DictionaryValue::~DictionaryValue() { + Clear(); +} + +void DictionaryValue::Clear() { + ValueMap::iterator dict_iterator = dictionary_.begin(); + while (dict_iterator != dictionary_.end()) { + delete dict_iterator->second; + ++dict_iterator; + } + + dictionary_.clear(); +} + +bool DictionaryValue::HasKey(const std::wstring& key) { + ValueMap::const_iterator current_entry = dictionary_.find(key); + DCHECK((current_entry == dictionary_.end()) || current_entry->second); + return current_entry != dictionary_.end(); +} + +void DictionaryValue::SetInCurrentNode(const std::wstring& key, + Value* in_value) { + // If there's an existing value here, we need to delete it, because + // we own all our children. + if (HasKey(key)) { + DCHECK(dictionary_[key] != in_value); // This would be bogus + delete dictionary_[key]; + } + + dictionary_[key] = in_value; +} + +bool DictionaryValue::Set(const std::wstring& path, Value* in_value) { + DCHECK(in_value); + + std::wstring key = path; + + size_t delimiter_position = path.find_first_of(L".", 0); + // If there isn't a dictionary delimiter in the path, we're done. + if (delimiter_position == std::wstring::npos) { + SetInCurrentNode(key, in_value); + return true; + } else { + key = path.substr(0, delimiter_position); + } + + // Assume that we're indexing into a dictionary. + DictionaryValue* entry = NULL; + ValueType current_entry_type = TYPE_NULL; + if (!HasKey(key) || (dictionary_[key]->GetType() != TYPE_DICTIONARY)) { + entry = new DictionaryValue; + SetInCurrentNode(key, entry); + } else { + entry = static_cast<DictionaryValue*>(dictionary_[key]); + } + + std::wstring remaining_path = path.substr(delimiter_position + 1); + return entry->Set(remaining_path, in_value); +} + +bool DictionaryValue::SetBoolean(const std::wstring& path, bool in_value) { + return Set(path, CreateBooleanValue(in_value)); +} + +bool DictionaryValue::SetInteger(const std::wstring& path, int in_value) { + return Set(path, CreateIntegerValue(in_value)); +} + +bool DictionaryValue::SetReal(const std::wstring& path, double in_value) { + return Set(path, CreateRealValue(in_value)); +} + +bool DictionaryValue::SetString(const std::wstring& path, + const std::wstring& in_value) { + return Set(path, CreateStringValue(in_value)); +} + +bool DictionaryValue::Get(const std::wstring& path, Value** out_value) const { + std::wstring key = path; + + size_t delimiter_position = path.find_first_of(L".", 0); + if (delimiter_position != std::wstring::npos) { + key = path.substr(0, delimiter_position); + } + + ValueMap::const_iterator entry_iterator = dictionary_.find(key); + if (entry_iterator == dictionary_.end()) + return false; + Value* entry = entry_iterator->second; + + if (delimiter_position == std::wstring::npos) { + if (out_value) + *out_value = entry; + return true; + } + + if (entry->IsType(TYPE_DICTIONARY)) { + DictionaryValue* dictionary = static_cast<DictionaryValue*>(entry); + return dictionary->Get(path.substr(delimiter_position + 1), out_value); + } + + return false; +} + +bool DictionaryValue::GetBoolean(const std::wstring& path, + bool* bool_value) const { + Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsBoolean(bool_value); +} + +bool DictionaryValue::GetInteger(const std::wstring& path, + int* out_value) const { + Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsInteger(out_value); +} + +bool DictionaryValue::GetReal(const std::wstring& path, + double* out_value) const { + Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsReal(out_value); +} + +bool DictionaryValue::GetString(const std::wstring& path, + std::wstring* out_value) const { + Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetBinary(const std::wstring& path, + BinaryValue** out_value) const { + Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_BINARY)) + return false; + + if (out_value) + *out_value = static_cast<BinaryValue*>(value); + + return true; +} + +bool DictionaryValue::GetDictionary(const std::wstring& path, + DictionaryValue** out_value) const { + Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast<DictionaryValue*>(value); + + return true; +} + +bool DictionaryValue::GetList(const std::wstring& path, + ListValue** out_value) const { + Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_LIST)) + return false; + + if (out_value) + *out_value = static_cast<ListValue*>(value); + + return true; +} + +bool DictionaryValue::Remove(const std::wstring& path, Value** out_value) { + std::wstring key = path; + + size_t delimiter_position = path.find_first_of(L".", 0); + if (delimiter_position != std::wstring::npos) { + key = path.substr(0, delimiter_position); + } + + ValueMap::iterator entry_iterator = dictionary_.find(key); + if (entry_iterator == dictionary_.end()) + return false; + Value* entry = entry_iterator->second; + + if (delimiter_position == std::wstring::npos) { + if (out_value) + *out_value = entry; + else + delete entry; + + dictionary_.erase(entry_iterator); + return true; + } + + if (entry->IsType(TYPE_DICTIONARY)) { + DictionaryValue* dictionary = static_cast<DictionaryValue*>(entry); + return dictionary->Remove(path.substr(delimiter_position + 1), out_value); + } + + return false; +} + +Value* DictionaryValue::DeepCopy() const { + DictionaryValue* result = new DictionaryValue; + + ValueMap::const_iterator current_entry = dictionary_.begin(); + while (current_entry != dictionary_.end()) { + result->Set(current_entry->first, current_entry->second->DeepCopy()); + ++current_entry; + } + + return result; +} + +bool DictionaryValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + const DictionaryValue* other_dict = + static_cast<const DictionaryValue*>(other); + key_iterator lhs_it(begin_keys()); + key_iterator rhs_it(other_dict->begin_keys()); + while (lhs_it != end_keys() && rhs_it != other_dict->end_keys()) { + Value* lhs; + Value* rhs; + if (!Get(*lhs_it, &lhs) || !other_dict->Get(*rhs_it, &rhs) || + !lhs->Equals(rhs)) { + return false; + } + ++lhs_it; + ++rhs_it; + } + if (lhs_it != end_keys() || rhs_it != other_dict->end_keys()) + return false; + + return true; +} + +///////////////////// ListValue //////////////////// + +ListValue::~ListValue() { + Clear(); +} + +void ListValue::Clear() { + ValueVector::iterator list_iterator = list_.begin(); + while (list_iterator != list_.end()) { + delete *list_iterator; + ++list_iterator; + } + 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, Value** out_value) const { + if (index >= list_.size()) + return false; + + if (out_value) + *out_value = list_[index]; + + return true; +} + +bool ListValue::GetDictionary(size_t index, + DictionaryValue** out_value) const { + Value* value; + bool result = Get(index, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast<DictionaryValue*>(value); + + return true; +} + +bool ListValue::Remove(size_t index, Value** out_value) { + if (index >= list_.size()) + return false; + + if (out_value) + *out_value = list_[index]; + else + delete list_[index]; + + ValueVector::iterator entry = list_.begin(); + entry += index; + + list_.erase(entry); + return true; +} + +void ListValue::Append(Value* in_value) { + DCHECK(in_value); + list_.push_back(in_value); +} + +Value* ListValue::DeepCopy() const { + ListValue* result = new ListValue; + + ValueVector::const_iterator current_entry = list_.begin(); + while (current_entry != list_.end()) { + result->Append((*current_entry)->DeepCopy()); + ++current_entry; + } + + return result; +} + +bool ListValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + const ListValue* other_list = + static_cast<const ListValue*>(other); + const_iterator lhs_it, rhs_it; + for (lhs_it = begin(), rhs_it = other_list->begin(); + lhs_it != end() && rhs_it != other_list->end(); + ++lhs_it, ++rhs_it) { + if (!(*lhs_it)->Equals(*rhs_it)) + return false; + } + if (lhs_it != end() || rhs_it != other_list->end()) + return false; + + return true; +} diff --git a/base/values.h b/base/values.h new file mode 100644 index 0000000..4f135d0 --- /dev/null +++ b/base/values.h @@ -0,0 +1,382 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file specifies a recursive data storage class called Value +// intended for storing setting and other persistable data. +// It includes the ability to specify (recursive) lists and dictionaries, so +// it's fairly expressive. However, the API is optimized for the common case, +// namely storing a hierarchical tree of simple values. Given a +// DictionaryValue root, you can easily do things like: +// +// root->SetString(L"global.pages.homepage", L"http://goateleporter.com"); +// std::wstring homepage = L"http://google.com"; // default/fallback value +// root->GetString(L"global.pages.homepage", &homepage); +// +// where "global" and "pages" are also DictionaryValues, and "homepage" +// is a string setting. If some elements of the path didn't exist yet, +// the SetString() method would create the missing elements and attach them +// to root before attaching the homepage value. + +#ifndef CHROME_COMMON_VALUES_H__ +#define CHROME_COMMON_VALUES_H__ + +#include <iterator> +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" + +class Value; +class FundamentalValue; +class StringValue; +class BinaryValue; +class DictionaryValue; +class ListValue; + +typedef std::vector<Value*> ValueVector; +typedef std::map<std::wstring, Value*> ValueMap; + +// The Value class is the base class for Values. A Value can be +// instantiated via the Create*Value() factory methods, or by directly +// creating instances of the subclasses. +class Value { + public: + virtual ~Value(); + + // Convenience methods for creating Value objects for various + // kinds of values without thinking about which class implements them. + // These can always be expected to return a valid Value*. + static Value* CreateNullValue(); + static Value* CreateBooleanValue(bool in_value); + static Value* CreateIntegerValue(int in_value); + static Value* CreateRealValue(double in_value); + static Value* CreateStringValue(const std::wstring& in_value); + + // This one can return NULL if the input isn't valid. If the return value + // is non-null, the new object has taken ownership of the buffer pointer. + static BinaryValue* CreateBinaryValue(char* buffer, size_t size); + + typedef enum { + TYPE_NULL = 0, + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_REAL, + TYPE_STRING, + TYPE_BINARY, + TYPE_DICTIONARY, + TYPE_LIST + } ValueType; + + // 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 ValueType to determine whether you can cast from + // Value* to (Implementing Class)*. Also, a Value object never changes + // its type after construction. + ValueType GetType() const { return type_; } + + // Returns true if the current object represents a given type. + bool IsType(ValueType type) const { return type == type_; } + + // These methods allow the convenient retrieval of settings. + // If the current setting object can be converted into the given type, + // the value is returned through the "value" parameter and true is returned; + // otherwise, false is returned and "value" is unchanged. + virtual bool GetAsBoolean(bool* out_value) const; + virtual bool GetAsInteger(int* out_value) const; + virtual bool GetAsReal(double* out_value) const; + virtual bool GetAsString(std::wstring* out_value) const; + + // This creates a deep copy of the entire Value tree, and returns a pointer + // to the copy. The caller gets ownership of the copy, of course. + virtual Value* DeepCopy() const; + + // Compares if two Value objects have equal contents. + virtual bool Equals(const Value* other) const; + + protected: + // This isn't safe for end-users (they should use the Create*Value() + // static methods above), but it's useful for subclasses. + Value(ValueType type) : type_(type) {} + + private: + DISALLOW_EVIL_CONSTRUCTORS(Value); + Value(); + + ValueType type_; +}; + +// FundamentalValue represents the simple fundamental types of values. +class FundamentalValue : public Value { + public: + FundamentalValue(bool in_value) + : Value(TYPE_BOOLEAN), boolean_value_(in_value) {} + FundamentalValue(int in_value) + : Value(TYPE_INTEGER), integer_value_(in_value) {} + FundamentalValue(double in_value) + : Value(TYPE_REAL), real_value_(in_value) {} + ~FundamentalValue(); + + // Subclassed methods + virtual bool GetAsBoolean(bool* out_value) const; + virtual bool GetAsInteger(int* out_value) const; + virtual bool GetAsReal(double* out_value) const; + virtual Value* DeepCopy() const; + virtual bool Equals(const Value* other) const; + + private: + DISALLOW_EVIL_CONSTRUCTORS(FundamentalValue); + + union { + bool boolean_value_; + int integer_value_; + double real_value_; + }; +}; + +class StringValue : public Value { + public: + StringValue(const std::wstring& in_value) + : Value(TYPE_STRING), value_(in_value) {} + ~StringValue(); + + // Subclassed methods + bool GetAsString(std::wstring* out_value) const; + Value* DeepCopy() const; + virtual bool Equals(const Value* other) const; + + private: + DISALLOW_EVIL_CONSTRUCTORS(StringValue); + + std::wstring value_; +}; + +class BinaryValue: public Value { +public: + // Creates a Value to represent a binary buffer. The new object takes + // ownership of the pointer passed in, if successful. + // Returns NULL if buffer is NULL. + static BinaryValue* Create(char* buffer, size_t size); + + // For situations where you want to keep ownership of your buffer, this + // factory method creates a new BinaryValue by copying the contents of the + // buffer that's passed in. + // Returns NULL if buffer is NULL. + static BinaryValue* CreateWithCopiedBuffer(char* buffer, size_t size); + + BinaryValue::~BinaryValue(); + + // Subclassed methods + Value* DeepCopy() const; + virtual bool Equals(const Value* other) const; + + size_t GetSize() const { return size_; } + char* GetBuffer() { return buffer_; } + +private: + DISALLOW_EVIL_CONSTRUCTORS(BinaryValue); + + // Constructor is private so that only objects with valid buffer pointers + // and size values can be created. + BinaryValue::BinaryValue(char* buffer, size_t size); + + char* buffer_; + size_t size_; +}; + +class DictionaryValue : public Value { + public: + DictionaryValue() : Value(TYPE_DICTIONARY) {} + ~DictionaryValue(); + + // Subclassed methods + Value* DeepCopy() const; + virtual bool Equals(const Value* other) const; + + // Returns true if the current dictionary has a value for the given key. + bool DictionaryValue::HasKey(const std::wstring& key); + + // Clears any current contents of this dictionary. + void DictionaryValue::Clear(); + + // Sets the Value associated with the given path starting from this object. + // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes + // into the next DictionaryValue down. Obviously, "." can't be used + // within a key, but there are no other restrictions on keys. + // If the key at any step of the way doesn't exist, or exists but isn't + // a DictionaryValue, a new DictionaryValue will be created and attached + // to the path in that location. + // Note that the dictionary takes ownership of the value + // referenced by in_value. + bool Set(const std::wstring& 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. + bool SetBoolean(const std::wstring& path, bool in_value); + bool SetInteger(const std::wstring& path, int in_value); + bool SetReal(const std::wstring& path, double in_value); + bool SetString(const std::wstring& path, const std::wstring& in_value); + + // Gets the Value associated with the given path starting from this object. + // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes + // into the next DictionaryValue down. If the path can be resolved + // successfully, the value for the last key in the path will be returned + // through the "value" parameter, and the function will return true. + // Otherwise, it will return false and "value" will be untouched. + // Note that the dictionary always owns the value that's returned. + bool Get(const std::wstring& path, Value** out_value) const; + + // 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::wstring& path, bool* out_value) const; + bool GetInteger(const std::wstring& path, int* out_value) const; + bool GetReal(const std::wstring& path, double* out_value) const; + bool GetString(const std::wstring& path, std::wstring* out_value) const; + bool GetBinary(const std::wstring& path, BinaryValue** out_value) const; + bool GetDictionary(const std::wstring& path, + DictionaryValue** out_value) const; + bool GetList(const std::wstring& path, ListValue** out_value) const; + + // Removes the Value with the specified path from this dictionary (or one + // of its child dictionaries, if the path is more than just a local key). + // If |out_value| is non-NULL, the removed Value AND ITS OWNERSHIP will be + // passed out via out_value. If |out_value| is NULL, the removed value will + // be deleted. This method returns true if |path| is a valid path; otherwise + // it will return false and the DictionaryValue object will be unchanged. + bool Remove(const std::wstring& path, Value** out_value); + + // This class provides an iterator for the keys in the dictionary. + // It can't be used to modify the dictionary. + class key_iterator + : private std::iterator<std::input_iterator_tag, const std::wstring> { + public: + key_iterator(ValueMap::const_iterator itr) { itr_ = itr; } + key_iterator operator++() { ++itr_; return *this; } + const std::wstring& operator*() { return itr_->first; } + bool operator!=(const key_iterator& other) { return itr_ != other.itr_; } + bool operator==(const key_iterator& other) { return itr_ == other.itr_; } + + private: + ValueMap::const_iterator itr_; + }; + + key_iterator begin_keys() const { return key_iterator(dictionary_.begin()); } + key_iterator end_keys() const { return key_iterator(dictionary_.end()); } + + private: + DISALLOW_EVIL_CONSTRUCTORS(DictionaryValue); + + // Associates the value |in_value| with the |key|. This method should be + // used instead of "dictionary_[key] = foo" so that any previous value can + // be properly deleted. + void SetInCurrentNode(const std::wstring& key, Value* in_value); + + ValueMap dictionary_; +}; + +// This type of Value represents a list of other Value values. +// TODO(jhughes): Flesh this out. +class ListValue : public Value { + public: + ListValue() : Value(TYPE_LIST) {} + ~ListValue(); + + // Subclassed methods + Value* DeepCopy() const; + virtual bool Equals(const Value* other) const; + + // Clears the contents of this ListValue + void Clear(); + + // Returns the number of Values in this list. + size_t GetSize() const { return list_.size(); } + + // 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 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, Value** out_value) const; + + // Convenience forms of Get(). Modifies value (and returns true) only if + // the index is valid and the Value at that index can be returned in + // the specified form. + bool GetDictionary(size_t index, DictionaryValue** out_value) const; + + // 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. + bool Remove(size_t index, Value** out_value); + + // Appends a Value to the end of the list. + void Append(Value* in_value); + + // Iteration + typedef ValueVector::iterator iterator; + typedef ValueVector::const_iterator const_iterator; + + ListValue::iterator begin() { return list_.begin(); } + ListValue::iterator end() { return list_.end(); } + + ListValue::const_iterator begin() const { return list_.begin(); } + ListValue::const_iterator end() const { return list_.end(); } + + ListValue::iterator Erase(iterator item) { + return list_.erase(item); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ListValue); + + ValueVector list_; +}; + +// This interface is implemented by classes that know how to serialize and +// deserialize Value objects. +class ValueSerializer { + public: + virtual bool Serialize(const Value& root) = 0; + + // This method deserializes the subclass-specific format into a Value object. + // The method should return true if and only if the root parameter is set + // to a complete Value representation of the serialized form. If the + // return value is true, the caller takes ownership of the objects pointed + // to by root. If the return value is false, root should be unchanged. + virtual bool Deserialize(Value** root) = 0; +}; + +#endif // CHROME_COMMON_VALUES_H__ diff --git a/base/values_unittest.cc b/base/values_unittest.cc new file mode 100644 index 0000000..1470a0d --- /dev/null +++ b/base/values_unittest.cc @@ -0,0 +1,403 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +class ValuesTest: public testing::Test { +}; + +TEST(ValuesTest, Basic) { + // Test basic dictionary getting/setting + DictionaryValue settings; + std::wstring homepage = L"http://google.com"; + ASSERT_FALSE( + settings.GetString(L"global.homepage", &homepage)); + ASSERT_EQ(std::wstring(L"http://google.com"), homepage); + + ASSERT_FALSE(settings.Get(L"global", NULL)); + ASSERT_TRUE(settings.Set(L"global", Value::CreateBooleanValue(true))); + ASSERT_TRUE(settings.Get(L"global", NULL)); + ASSERT_TRUE(settings.SetString(L"global.homepage", L"http://scurvy.com")); + ASSERT_TRUE(settings.Get(L"global", NULL)); + homepage = L"http://google.com"; + ASSERT_TRUE(settings.GetString(L"global.homepage", &homepage)); + ASSERT_EQ(std::wstring(L"http://scurvy.com"), homepage); + + // Test storing a dictionary in a list. + ListValue* toolbar_bookmarks; + ASSERT_FALSE( + settings.GetList(L"global.toolbar.bookmarks", &toolbar_bookmarks)); + + toolbar_bookmarks = new ListValue; + settings.Set(L"global.toolbar.bookmarks", toolbar_bookmarks); + ASSERT_TRUE( + settings.GetList(L"global.toolbar.bookmarks", &toolbar_bookmarks)); + + DictionaryValue* new_bookmark = new DictionaryValue; + new_bookmark->SetString(L"name", L"Froogle"); + new_bookmark->SetString(L"url", L"http://froogle.com"); + toolbar_bookmarks->Append(new_bookmark); + + ListValue* bookmark_list; + ASSERT_TRUE(settings.GetList(L"global.toolbar.bookmarks", &bookmark_list)); + DictionaryValue* bookmark; + ASSERT_EQ(1, bookmark_list->GetSize()); + ASSERT_TRUE(bookmark_list->GetDictionary(0, &bookmark)); + std::wstring bookmark_name = L"Unnamed"; + ASSERT_TRUE(bookmark->GetString(L"name", &bookmark_name)); + ASSERT_EQ(std::wstring(L"Froogle"), bookmark_name); + std::wstring bookmark_url; + ASSERT_TRUE(bookmark->GetString(L"url", &bookmark_url)); + ASSERT_EQ(std::wstring(L"http://froogle.com"), bookmark_url); +} + +TEST(ValuesTest, BinaryValue) { + char* buffer = NULL; + // Passing a null buffer pointer doesn't yield a BinaryValue + BinaryValue* binary = BinaryValue::Create(buffer, 0); + ASSERT_FALSE(binary); + + // If you want to represent an empty binary value, use a zero-length buffer. + buffer = new char[0]; + ASSERT_TRUE(buffer); + binary = BinaryValue::Create(buffer, 0); + ASSERT_TRUE(binary); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_EQ(buffer, binary->GetBuffer()); + ASSERT_EQ(0, binary->GetSize()); + delete binary; + binary = NULL; + + // Test the common case of a non-empty buffer + buffer = new char[15]; + binary = BinaryValue::Create(buffer, 15); + ASSERT_TRUE(binary); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_EQ(buffer, binary->GetBuffer()); + ASSERT_EQ(15, binary->GetSize()); + delete binary; + binary = NULL; + + char stack_buffer[42]; + memset(stack_buffer, '!', 42); + binary = BinaryValue::CreateWithCopiedBuffer(stack_buffer, 42); + ASSERT_TRUE(binary); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_NE(stack_buffer, binary->GetBuffer()); + ASSERT_EQ(42, binary->GetSize()); + ASSERT_EQ(0, memcmp(stack_buffer, binary->GetBuffer(), binary->GetSize())); + delete binary; +} + +// 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: + 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; + } + + ~DeletionTestValue() { + *deletion_flag_ = true; + } + +private: + bool* deletion_flag_; +}; + +TEST(ValuesTest, ListDeletion) { + bool deletion_flag = true; + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + } + EXPECT_TRUE(deletion_flag); + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + list.Clear(); + EXPECT_TRUE(deletion_flag); + } + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(list.Set(0, Value::CreateNullValue())); + EXPECT_TRUE(deletion_flag); + } +} + +TEST(ValuesTest, ListRemoval) { + bool deletion_flag = true; + Value* removed_item = NULL; + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_EQ(1, list.GetSize()); + EXPECT_FALSE(list.Remove(-1, &removed_item)); + EXPECT_FALSE(list.Remove(1, &removed_item)); + EXPECT_TRUE(list.Remove(0, &removed_item)); + ASSERT_TRUE(removed_item); + EXPECT_EQ(0, list.GetSize()); + } + EXPECT_FALSE(deletion_flag); + delete removed_item; + removed_item = NULL; + EXPECT_TRUE(deletion_flag); + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(list.Remove(0, NULL)); + EXPECT_TRUE(deletion_flag); + EXPECT_EQ(0, list.GetSize()); + } +} + +TEST(ValuesTest, DictionaryDeletion) { + std::wstring key = L"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::wstring key = L"test"; + bool deletion_flag = true; + Value* removed_item = NULL; + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(dict.HasKey(key)); + EXPECT_FALSE(dict.Remove(L"absent key", &removed_item)); + EXPECT_TRUE(dict.Remove(key, &removed_item)); + EXPECT_FALSE(dict.HasKey(key)); + ASSERT_TRUE(removed_item); + } + EXPECT_FALSE(deletion_flag); + delete removed_item; + removed_item = NULL; + EXPECT_TRUE(deletion_flag); + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(dict.HasKey(key)); + EXPECT_TRUE(dict.Remove(key, NULL)); + EXPECT_TRUE(deletion_flag); + EXPECT_FALSE(dict.HasKey(key)); + } +} + +TEST(ValuesTest, DeepCopy) { + DictionaryValue original_dict; + Value* original_null = Value::CreateNullValue(); + original_dict.Set(L"null", original_null); + Value* original_bool = Value::CreateBooleanValue(true); + original_dict.Set(L"bool", original_bool); + Value* original_int = Value::CreateIntegerValue(42); + original_dict.Set(L"int", original_int); + Value* original_real = Value::CreateRealValue(3.14); + original_dict.Set(L"real", original_real); + Value* original_string = Value::CreateStringValue(L"peek-a-boo"); + original_dict.Set(L"string", original_string); + + char* original_buffer = new char[42]; + memset(original_buffer, '!', 42); + BinaryValue* original_binary = Value::CreateBinaryValue(original_buffer, 42); + original_dict.Set(L"binary", original_binary); + + ListValue* original_list = new ListValue(); + Value* original_list_element_0 = Value::CreateIntegerValue(0); + original_list->Append(original_list_element_0); + Value* original_list_element_1 = Value::CreateIntegerValue(1); + original_list->Append(original_list_element_1); + original_dict.Set(L"list", original_list); + + DictionaryValue* copy_dict = + static_cast<DictionaryValue*>(original_dict.DeepCopy()); + ASSERT_TRUE(copy_dict); + ASSERT_NE(copy_dict, &original_dict); + + Value* copy_null = NULL; + ASSERT_TRUE(copy_dict->Get(L"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(L"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(L"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_real = NULL; + ASSERT_TRUE(copy_dict->Get(L"real", ©_real)); + ASSERT_TRUE(copy_real); + ASSERT_NE(copy_real, original_real); + ASSERT_TRUE(copy_real->IsType(Value::TYPE_REAL)); + double copy_real_value = 0; + ASSERT_TRUE(copy_real->GetAsReal(©_real_value)); + ASSERT_EQ(3.14, copy_real_value); + + Value* copy_string = NULL; + ASSERT_TRUE(copy_dict->Get(L"string", ©_string)); + ASSERT_TRUE(copy_string); + ASSERT_NE(copy_string, original_string); + ASSERT_TRUE(copy_string->IsType(Value::TYPE_STRING)); + std::wstring copy_string_value; + ASSERT_TRUE(copy_string->GetAsString(©_string_value)); + ASSERT_EQ(std::wstring(L"peek-a-boo"), copy_string_value); + + Value* copy_binary = NULL; + ASSERT_TRUE(copy_dict->Get(L"binary", ©_binary)); + ASSERT_TRUE(copy_binary); + ASSERT_NE(copy_binary, original_binary); + ASSERT_TRUE(copy_binary->IsType(Value::TYPE_BINARY)); + ASSERT_NE(original_binary->GetBuffer(), + static_cast<BinaryValue*>(copy_binary)->GetBuffer()); + ASSERT_EQ(original_binary->GetSize(), + static_cast<BinaryValue*>(copy_binary)->GetSize()); + ASSERT_EQ(0, memcmp(original_binary->GetBuffer(), + static_cast<BinaryValue*>(copy_binary)->GetBuffer(), + original_binary->GetSize())); + + Value* copy_value = NULL; + ASSERT_TRUE(copy_dict->Get(L"list", ©_value)); + ASSERT_TRUE(copy_value); + ASSERT_NE(copy_value, original_list); + ASSERT_TRUE(copy_value->IsType(Value::TYPE_LIST)); + ListValue* copy_list = static_cast<ListValue*>(copy_value); + ASSERT_EQ(2, 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); + + delete copy_dict; +} + +TEST(ValuesTest, Equals) { + Value* null1 = Value::CreateNullValue(); + Value* null2 = Value::CreateNullValue(); + EXPECT_NE(null1, null2); + EXPECT_TRUE(null1->Equals(null2)); + + Value* boolean = Value::CreateBooleanValue(false); + EXPECT_FALSE(null1->Equals(boolean)); + delete null1; + delete null2; + delete boolean; + + DictionaryValue dv; + dv.SetBoolean(L"a", false); + dv.SetInteger(L"b", 2); + dv.SetReal(L"c", 2.5); + dv.SetString(L"d", L"string"); + dv.Set(L"e", Value::CreateNullValue()); + + DictionaryValue* copy = static_cast<DictionaryValue*>(dv.DeepCopy()); + EXPECT_TRUE(dv.Equals(copy)); + + ListValue* list = new ListValue; + list->Append(Value::CreateNullValue()); + list->Append(new DictionaryValue); + dv.Set(L"f", list); + + EXPECT_FALSE(dv.Equals(copy)); + copy->Set(L"f", list->DeepCopy()); + EXPECT_TRUE(dv.Equals(copy)); + + list->Append(Value::CreateBooleanValue(true)); + EXPECT_FALSE(dv.Equals(copy)); + delete copy; +} diff --git a/base/watchdog.cc b/base/watchdog.cc new file mode 100644 index 0000000..1d9c183 --- /dev/null +++ b/base/watchdog.cc @@ -0,0 +1,171 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/watchdog.h" +#include "base/string_util.h" +#include "base/thread.h" + +//------------------------------------------------------------------------------ +// Public API methods. + +// Start thread running in a Disarmed state. +Watchdog::Watchdog(const TimeDelta& duration, + const std::wstring& thread_watched_name, + bool enabled) + : lock_(), + condition_variable_(&lock_), + state_(DISARMED), + duration_(duration), + thread_watched_name_(thread_watched_name), + handle_(NULL), + thread_id_(0) { + if (!enabled) + return; // Don't start thread, or doing anything really. + handle_ = CreateThread(NULL, // security + 0, // Default stack size. + Watchdog::ThreadStart, + reinterpret_cast<void*>(this), + CREATE_SUSPENDED, + &thread_id_); + DCHECK(NULL != handle_); + if (NULL == handle_) + return ; + ResumeThread(handle_); // WINAPI call. +} + +// Notify watchdog thread, and wait for it to finish up. +Watchdog::~Watchdog() { + if (NULL == handle_) + return; + { + AutoLock lock(lock_); + state_ = SHUTDOWN; + } + condition_variable_.Signal(); + DWORD results = WaitForSingleObject(handle_, INFINITE); + DCHECK(WAIT_OBJECT_0 == results); + CloseHandle(handle_); + handle_ = NULL; +} + +void Watchdog::Arm() { + ArmAtStartTime(TimeTicks::Now()); +} + +void Watchdog::ArmSomeTimeDeltaAgo(const TimeDelta& time_delta) { + ArmAtStartTime(TimeTicks::Now() - time_delta); +} + +// Start clock for watchdog. +void Watchdog::ArmAtStartTime(const TimeTicks start_time) { + { + AutoLock lock(lock_); + start_time_ = start_time; + state_ = ARMED; + } + // Force watchdog to wake up, and go to sleep with the timer ticking with the + // proper duration. + condition_variable_.Signal(); +} + +// Disable watchdog so that it won't do anything when time expires. +void Watchdog::Disarm() { + if (NULL == handle_) + return; + AutoLock lock(lock_); + state_ = DISARMED; + // We don't need to signal, as the watchdog will eventually wake up, and it + // will check its state and time, and act accordingly. +} + +//------------------------------------------------------------------------------ +// Internal private methods that the watchdog thread uses. + +// static +DWORD __stdcall Watchdog::ThreadStart(void* pThis) { + Watchdog* watchdog = reinterpret_cast<Watchdog*>(pThis); + return watchdog->Run(); +} + +unsigned Watchdog::Run() { + SetThreadName(); + TimeDelta remaining_duration; + while (1) { + AutoLock lock(lock_); + while (DISARMED == state_) + condition_variable_.Wait(); + if (SHUTDOWN == state_) + return 0; + DCHECK(ARMED == state_); + remaining_duration = duration_ - (TimeTicks::Now() - start_time_); + if (remaining_duration.InMilliseconds() > 0) { + // Spurios wake? Timer drifts? Go back to sleep for remaining time. + condition_variable_.TimedWait(remaining_duration); + } else { + // We overslept, so this seems like a real alarm. + // Watch out for a user that stopped the debugger on a different alarm! + { + AutoLock static_lock(static_lock_); + if (last_debugged_alarm_time_ > start_time_) { + // False alarm: we started our clock before the debugger break (last + // alarm time). + start_time_ += last_debugged_alarm_delay_; + if (last_debugged_alarm_time_ > start_time_) + state_ = DISARMED; // Too many alarms must have taken place. + continue; + } + } + state_ = DISARMED; // Only alarm at most once. + TimeTicks last_alarm_time = TimeTicks::Now(); + Alarm(); // Set a break point here to debug on alarms. + TimeDelta last_alarm_delay = TimeTicks::Now() - last_alarm_time; + if (last_alarm_delay > TimeDelta::FromMilliseconds(2)) { + // Ignore race of two alarms/breaks going off at roughly the same time. + AutoLock static_lock(static_lock_); + // This was a real debugger break. + last_debugged_alarm_time_ = last_alarm_time; + last_debugged_alarm_delay_ = last_alarm_delay; + } + } + } +} + +void Watchdog::SetThreadName() const { + std::string name = StringPrintf("%s Watchdog", + WideToASCII(thread_watched_name_).c_str()); + Thread::SetThreadName(name.c_str(), thread_id_); + DLOG(INFO) << "Watchdog active: " << name; +} + +// static +Lock Watchdog::static_lock_; // Lock for access of static data... +// static +TimeTicks Watchdog::last_debugged_alarm_time_ = TimeTicks(); +// static +TimeDelta Watchdog::last_debugged_alarm_delay_; diff --git a/base/watchdog.h b/base/watchdog.h new file mode 100644 index 0000000..2871941 --- /dev/null +++ b/base/watchdog.h @@ -0,0 +1,111 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The Watchdog class creates a second thread that can Alarm if a specific +// duration of time passes without proper attention. The duration of time is +// specified at construction time. The Watchdog may be used many times by +// simply calling Arm() (to start timing) and Disarm() (to reset the timer). +// The Watchdog is typically used under a debugger, where the stack traces on +// other threads can be examined if/when the Watchdog alarms. + +// Some watchdogs will be enabled or disabled via command line switches. To +// facilitate such code, an "enabled" argument for the constuctor can be used +// to permanently disable the watchdog. Disabled watchdogs don't even spawn +// a second thread, and their methods call (Arm() and Disarm()) return very +// quickly. + +#ifndef BASE_WATCHDOG_H__ +#define BASE_WATCHDOG_H__ + +#include <string> + +#include "base/condition_variable.h" +#include "base/lock.h" +#include "base/time.h" + +class Watchdog { + public: + // TODO(JAR)change default arg to required arg after all users have migrated. + // Constructor specifies how long the Watchdog will wait before alarming. + Watchdog(const TimeDelta& duration, + const std::wstring& thread_watched_name, + bool enabled = true); + virtual ~Watchdog(); + + // Start timing, and alarm when time expires (unless we're disarm()ed.) + void Arm(); // Arm starting now. + void ArmSomeTimeDeltaAgo(const TimeDelta& time_delta); + void ArmAtStartTime(const TimeTicks start_time); + + // Reset time, and do not set off the alarm. + void Disarm(); + + // Alarm is called if the time expires after an Arm() without someone calling + // Disarm(). This method can be overridden to create testable classes. + virtual void Alarm() { + DLOG(INFO) << "Watchdog alarmed for " << thread_watched_name_; + } + + private: + enum State {ARMED, DISARMED, SHUTDOWN }; + + // Windows thread start callback + static DWORD WINAPI ThreadStart(void* pThis); + + // Loop and test function for our watchdog thread. + unsigned Run(); + void Watchdog::SetThreadName() const; + + Lock lock_; // Mutex for state_. + ConditionVariable condition_variable_; + State state_; + const TimeDelta duration_; // How long after start_time_ do we alarm? + const std::wstring thread_watched_name_; + HANDLE handle_; // Handle for watchdog thread. + DWORD thread_id_; // Also for watchdog thread. + + TimeTicks start_time_; // Start of epoch, and alarm after duration_. + + // When the debugger breaks (when we alarm), all the other alarms that are + // armed will expire (also alarm). To diminish this effect, we track any + // delay due to debugger breaks, and we *try* to adjust the effective start + // time of other alarms to step past the debugging break. + // Without this safety net, any alarm will typically trigger a host of follow + // on alarms from callers that specify old times. + static Lock static_lock_; // Lock for access of static data... + // When did we last alarm and get stuck (for a while) in a debugger? + static TimeTicks last_debugged_alarm_time_; + // How long did we sit on a break in the debugger? + static TimeDelta last_debugged_alarm_delay_; + + + DISALLOW_EVIL_CONSTRUCTORS(Watchdog); +}; + +#endif // BASE_WATCHDOG_H__ diff --git a/base/watchdog_test.cc b/base/watchdog_test.cc new file mode 100644 index 0000000..3f569af --- /dev/null +++ b/base/watchdog_test.cc @@ -0,0 +1,153 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Tests for Watchdog class. + +#include "base/logging.h" +#include "base/watchdog.h" +#include "base/spin_wait.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +//------------------------------------------------------------------------------ +// Provide a derived class to facilitate testing. + +// TODO(JAR): Remove default argument from constructor, and make mandatory. +class WatchdogCounter : public Watchdog { + public: + WatchdogCounter(const TimeDelta& duration, + const std::wstring& thread_watched_name, + bool enabled = true) + : Watchdog(duration, thread_watched_name, enabled), alarm_counter_(0) { + } + + virtual ~WatchdogCounter() {} + + virtual void Alarm() { + alarm_counter_++; + Watchdog::Alarm(); + } + + int alarm_counter() { return alarm_counter_; } + + private: + int alarm_counter_; + + DISALLOW_EVIL_CONSTRUCTORS(WatchdogCounter); +}; + +class WatchdogTest : public testing::Test { +}; + + +//------------------------------------------------------------------------------ +// Actual tests + +// Minimal constructor/destructor test. +TEST(WatchdogTest, StartupShutdownTest) { + Watchdog watchdog1(TimeDelta::FromMilliseconds(300), L"Disabled", false); + Watchdog watchdog2(TimeDelta::FromMilliseconds(300), L"Enabled", true); + + // The following test is depricated, and should be removed when the + // default argument constructor is no longer accepted. + Watchdog watchdog3(TimeDelta::FromMilliseconds(300), L"Default"); +} + +// Test ability to call Arm and Disarm repeatedly. +TEST(WatchdogTest, ArmDisarmTest) { + Watchdog watchdog1(TimeDelta::FromMilliseconds(300), L"Disabled", false); + watchdog1.Arm(); + watchdog1.Disarm(); + watchdog1.Arm(); + watchdog1.Disarm(); + + Watchdog watchdog2(TimeDelta::FromMilliseconds(300), L"Enabled", true); + watchdog2.Arm(); + watchdog2.Disarm(); + watchdog2.Arm(); + watchdog2.Disarm(); + + // The following test is depricated, and should be removed when the + // default argument constructor is no longer accepted. + Watchdog watchdog3(TimeDelta::FromMilliseconds(300), L"Default"); + watchdog3.Arm(); + watchdog3.Disarm(); + watchdog3.Arm(); + watchdog3.Disarm(); +} + +// Make sure a basic alarm fires when the time has expired. +TEST(WatchdogTest, AlarmTest) { + WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), L"Enabled", true); + watchdog.Arm(); + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromSeconds(1), + watchdog.alarm_counter() > 0); + EXPECT_EQ(1, watchdog.alarm_counter()); + + // Set a time greater than the timeout into the past. + watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(2)); + // It should instantly go off, but certainly in less than a second. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromSeconds(1), + watchdog.alarm_counter() > 1); + + EXPECT_EQ(2, watchdog.alarm_counter()); +} + +// Make sure a disable alarm does nothing, even if we arm it. +TEST(WatchdogTest, ConstructorDisabledTest) { + WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), L"Disabled", false); + watchdog.Arm(); + // Alarm should not fire, as it was disabled. + Sleep(500); + EXPECT_EQ(0, watchdog.alarm_counter()); +} + +// Make sure Disarming will prevent firing, even after Arming. +TEST(WatchdogTest, DisarmTest) { + WatchdogCounter watchdog(TimeDelta::FromSeconds(1), L"Enabled", true); + watchdog.Arm(); + Sleep(100); // Don't sleep too long + watchdog.Disarm(); + // Alarm should not fire. + Sleep(1500); + EXPECT_EQ(0, watchdog.alarm_counter()); + + // ...but even after disarming, we can still use the alarm... + // Set a time greater than the timeout into the past. + watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(2)); + // It should almost instantly go off, but certainly in less than a second. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromSeconds(1), + watchdog.alarm_counter() > 0); + + EXPECT_EQ(1, watchdog.alarm_counter()); +} + +} // namespace diff --git a/base/win_util.cc b/base/win_util.cc new file mode 100644 index 0000000..478a5a6 --- /dev/null +++ b/base/win_util.cc @@ -0,0 +1,359 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/win_util.h" + +#include <sddl.h> + +#include "base/logging.h" +#include "base/registry.h" +#include "base/scoped_handle.h" +#include "base/string_util.h" + +namespace win_util { + +#define SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(struct_name, member) \ + offsetof(struct_name, member) + \ + (sizeof static_cast<struct_name*>(NULL)->member) +#define NONCLIENTMETRICS_SIZE_PRE_VISTA \ + SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(NONCLIENTMETRICS, lfMessageFont) + +void GetNonClientMetrics(NONCLIENTMETRICS* metrics) { + DCHECK(metrics); + + static const UINT SIZEOF_NONCLIENTMETRICS = + (GetWinVersion() == WINVERSION_VISTA) ? + sizeof(NONCLIENTMETRICS) : NONCLIENTMETRICS_SIZE_PRE_VISTA; + metrics->cbSize = SIZEOF_NONCLIENTMETRICS; + const bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, + SIZEOF_NONCLIENTMETRICS, metrics, + 0); + DCHECK(success); +} + +WinVersion GetWinVersion() { + static bool checked_version = false; + static WinVersion win_version = WINVERSION_PRE_2000; + if (!checked_version) { + OSVERSIONINFO version_info; + version_info.dwOSVersionInfoSize = sizeof version_info; + GetVersionEx(&version_info); + if (version_info.dwMajorVersion == 5) { + switch (version_info.dwMinorVersion) { + case 0: + win_version = WINVERSION_2000; + break; + case 1: + win_version = WINVERSION_XP; + break; + case 2: + default: + win_version = WINVERSION_SERVER_2003; + break; + } + } else if (version_info.dwMajorVersion >= 6) { + win_version = WINVERSION_VISTA; + } + checked_version = true; + } + return win_version; +} + +bool AddAccessToKernelObject(HANDLE handle, WELL_KNOWN_SID_TYPE known_sid, + ACCESS_MASK access) { + PSECURITY_DESCRIPTOR descriptor = NULL; + PACL old_dacl = NULL; + PACL new_dacl = NULL; + + if (ERROR_SUCCESS != GetSecurityInfo(handle, SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl, NULL, + &descriptor)) + return false; + + BYTE sid[SECURITY_MAX_SID_SIZE] = {0}; + DWORD size_sid = SECURITY_MAX_SID_SIZE; + + if (known_sid == WinSelfSid) { + // We hijack WinSelfSid when we want to add the current user instead of + // a known sid. + HANDLE token = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) { + LocalFree(descriptor); + return false; + } + + DWORD size = sizeof(TOKEN_USER) + size_sid; + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(new BYTE[size]); + scoped_ptr<TOKEN_USER> token_user_ptr(token_user); + BOOL ret = GetTokenInformation(token, TokenUser, token_user, size, &size); + + CloseHandle(token); + + if (!ret) { + LocalFree(descriptor); + return false; + } + memcpy(sid, token_user->User.Sid, size_sid); + } else { + if (!CreateWellKnownSid(known_sid , NULL, sid, &size_sid)) { + LocalFree(descriptor); + return false; + } + } + + EXPLICIT_ACCESS new_access = {0}; + new_access.grfAccessMode = GRANT_ACCESS; + new_access.grfAccessPermissions = access; + new_access.grfInheritance = NO_INHERITANCE; + + new_access.Trustee.pMultipleTrustee = NULL; + new_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + new_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; + new_access.Trustee.ptstrName = reinterpret_cast<LPWSTR>(&sid); + + if (ERROR_SUCCESS != SetEntriesInAcl(1, &new_access, old_dacl, &new_dacl)) { + LocalFree(descriptor); + return false; + } + + DWORD result = SetSecurityInfo(handle, SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, NULL, NULL, + new_dacl, NULL); + + LocalFree(new_dacl); + LocalFree(descriptor); + + if (ERROR_SUCCESS != result) + return false; + + return true; +} + +bool GetUserSidString(std::wstring* user_sid) { + // Get the current token. + HANDLE token = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) + return false; + ScopedHandle token_scoped(token); + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + scoped_ptr<TOKEN_USER> user(reinterpret_cast<TOKEN_USER*>(new BYTE[size])); + + if (!::GetTokenInformation(token, TokenUser, user.get(), 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 GetLogonSessionOnlyDACL(SECURITY_DESCRIPTOR** security_descriptor) { + // Get the current token. + HANDLE token = NULL; + if (!OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) + return false; + ScopedHandle token_scoped(token); + + // Get the size of the TokenGroups structure. + DWORD size = 0; + BOOL result = GetTokenInformation(token, TokenGroups, NULL, 0, &size); + if (result != FALSE && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; + + // Get the data. + scoped_ptr<TOKEN_GROUPS> token_groups; + token_groups.reset(reinterpret_cast<TOKEN_GROUPS*>(new char[size])); + + if (!GetTokenInformation(token, TokenGroups, token_groups.get(), size, &size)) + return false; + + // Look for the logon sid. + SID* logon_sid = NULL; + for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) != 0) { + logon_sid = static_cast<SID*>(token_groups->Groups[i].Sid); + break; + } + } + + if (!logon_sid) + return false; + + // Convert the data to a string. + wchar_t* sid_string; + if (!ConvertSidToStringSid(logon_sid, &sid_string)) + return false; + + static const wchar_t dacl_format[] = L"D:(A;OICI;GA;;;%s)"; + wchar_t dacl[SECURITY_MAX_SID_SIZE + arraysize(dacl_format) + 1] = {0}; + wsprintf(dacl, dacl_format, sid_string); + + LocalFree(sid_string); + + // Convert the string to a security descriptor + if (!ConvertStringSecurityDescriptorToSecurityDescriptor( + dacl, + SDDL_REVISION_1, + reinterpret_cast<PSECURITY_DESCRIPTOR*>(security_descriptor), + NULL)) { + return false; + } + + return true; +} + +#pragma warning(push) +#pragma warning(disable:4312 4244) +WNDPROC SetWindowProc(HWND hwnd, WNDPROC proc) { + // The reason we don't return the SetwindowLongPtr() value is that it returns + // the orignal window procedure and not the current one. I don't know if it is + // a bug or an intended feature. + WNDPROC oldwindow_proc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(proc)); + return oldwindow_proc; +} + +void* SetWindowUserData(HWND hwnd, void* user_data) { + return + reinterpret_cast<void*>(SetWindowLongPtr(hwnd, GWLP_USERDATA, + reinterpret_cast<LONG_PTR>(user_data))); +} + +void* GetWindowUserData(HWND hwnd) { + return reinterpret_cast<void*>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); +} + +// Maps to the WNDPROC for a window that was active before the subclass was +// installed. +static const wchar_t* const kHandlerKey = L"__ORIGINAL_MESSAGE_HANDLER__"; + +bool Subclass(HWND window, WNDPROC subclass_proc) { + WNDPROC original_handler = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(window, GWLP_WNDPROC)); + if (original_handler != subclass_proc) { + win_util::SetWindowProc(window, subclass_proc); + SetProp(window, kHandlerKey, original_handler); + return true; + } + return false; +} + +bool Unsubclass(HWND window, WNDPROC subclass_proc) { + WNDPROC current_handler = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(window, GWLP_WNDPROC)); + if (current_handler == subclass_proc) { + HANDLE original_handler = GetProp(window, kHandlerKey); + if (original_handler) { + RemoveProp(window, kHandlerKey); + win_util::SetWindowProc(window, + reinterpret_cast<WNDPROC>(original_handler)); + return true; + } + } + return false; +} + +WNDPROC GetSuperclassWNDPROC(HWND window) { + return reinterpret_cast<WNDPROC>(GetProp(window, kHandlerKey)); +} + +#pragma warning(pop) + +bool IsShiftPressed() { + return (::GetKeyState(VK_SHIFT) & 0x80) == 0x80; +} + +bool IsCtrlPressed() { + return (::GetKeyState(VK_CONTROL) & 0x80) == 0x80; +} + +bool IsAltPressed() { + return (::GetKeyState(VK_MENU) & 0x80) == 0x80; +} + +std::wstring GetClassName(HWND window) { + // GetClassNameW will return a truncated result (properly null terminated) if + // the given buffer is not large enough. So, it is not possible to determine + // that we got the entire class name if the result is exactly equal to the + // size of the buffer minus one. + DWORD buffer_size = MAX_PATH; + while (true) { + std::wstring output; + DWORD size_ret = + GetClassNameW(window, WriteInto(&output, buffer_size), buffer_size); + if (size_ret == 0) + break; + if (size_ret < (buffer_size - 1)) { + output.resize(size_ret); + return output; + } + buffer_size *= 2; + } + return std::wstring(); // error +} + +bool UserAccountControlIsEnabled() { + RegKey key(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"); + DWORD uac_enabled; + if (!key.ReadValueDW(L"EnableLUA", &uac_enabled)) + return true; + return (uac_enabled == 1); +} + +} // namespace win_util + +#ifdef _MSC_VER +// +// If the ASSERT below fails, please install Visual Studio 2005 Service Pack 1. +// +extern char VisualStudio2005ServicePack1Detection[10]; +COMPILE_ASSERT(sizeof(&VisualStudio2005ServicePack1Detection) == 4, + VS2005SP1Detect); +// +// Chrome requires at least Service Pack 1 for Visual Studio 2005. +// +#endif // _MSC_VER + +#ifndef COPY_FILE_COPY_SYMLINK +#error You must install the Windows 2008 or Vista Software Development Kit and \ +set it as your default include path to build this library. You can grab it by \ +searching for "download windows sdk 2008" in your favorite web search engine. +#endif diff --git a/base/win_util.h b/base/win_util.h new file mode 100644 index 0000000..9efb245 --- /dev/null +++ b/base/win_util.h @@ -0,0 +1,116 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_WIN_UTIL_H__ +#define BASE_WIN_UTIL_H__ + +#include <windows.h> +#include <aclapi.h> + +#include <string> + +namespace win_util { + +// NOTE: Keep these in order so callers can do things like +// "if (GetWinVersion() > WINVERSION_2000) ...". It's OK to change the values, +// though. +enum WinVersion { + WINVERSION_PRE_2000 = 0, // Not supported + WINVERSION_2000 = 1, + WINVERSION_XP = 2, + WINVERSION_SERVER_2003 = 3, + WINVERSION_VISTA = 4, +}; + +void GetNonClientMetrics(NONCLIENTMETRICS* metrics); + +// Returns the running version of Windows. +WinVersion GetWinVersion(); + +// Adds an ACE in the DACL of the object referenced by handle. The ACE is +// granting |access| to the user |known_sid|. +// If |known_sid| is WinSelfSid, the sid of the current user will be added to +// the DACL. +bool AddAccessToKernelObject(HANDLE handle, WELL_KNOWN_SID_TYPE known_sid, + ACCESS_MASK access); + +// Returns the string representing the current user sid. +bool GetUserSidString(std::wstring* user_sid); + +// Creates a security descriptor with a DACL that has one ace giving full +// access to the current logon session. +// The security descriptor returned must be freed using LocalFree. +// The function returns true if it succeeds, false otherwise. +bool GetLogonSessionOnlyDACL(SECURITY_DESCRIPTOR** security_descriptor); + +// Useful for subclassing a HWND. Returns the previous window procedure. +WNDPROC SetWindowProc(HWND hwnd, WNDPROC wndproc); + +// Subclasses a window, replacing its existing window procedure with the +// specified one. Returns true if the current window procedure was replaced, +// false if the window has already been subclassed with the specified +// subclass procedure. +bool Subclass(HWND window, WNDPROC subclass_proc); + +// Unsubclasses a window subclassed using Subclass. Returns true if +// the window was subclassed with the specified |subclass_proc| and the window +// was successfully unsubclassed, false if the window's window procedure is not +// |subclass_proc|. +bool Unsubclass(HWND window, WNDPROC subclass_proc); + +// Retrieves the original WNDPROC of a window subclassed using +// SubclassWindow. +WNDPROC GetSuperclassWNDPROC(HWND window); + +// Pointer-friendly wrappers around Get/SetWindowLong(..., GWLP_USERDATA, ...) +// Returns the previously set value. +void* SetWindowUserData(HWND hwnd, void* user_data); +void* GetWindowUserData(HWND hwnd); + +// Returns true if the shift key is currently pressed. +bool IsShiftPressed(); + +// Returns true if the ctrl key is currently pressed. +bool IsCtrlPressed(); + +// Returns true if the alt key is currently pressed. +bool IsAltPressed(); + +// A version of the GetClassNameW API that returns the class name in an +// std::wstring. An empty result indicates a failure to get the class name. +std::wstring GetClassName(HWND window); + +// Returns false if the computer is running Vista and the user account control +// is disabled. Returns true if user account control is enabled or the machine +// is not running vista. +bool UserAccountControlIsEnabled(); + +} // namespace win_util + +#endif // BASE_WIN_UTIL_H__ diff --git a/base/win_util_unittest.cc b/base/win_util_unittest.cc new file mode 100644 index 0000000..2374284 --- /dev/null +++ b/base/win_util_unittest.cc @@ -0,0 +1,57 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/win_util.h" + +// 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 (win_util::GetWinVersion() == win_util::WINVERSION_VISTA) { + win_util::UserAccountControlIsEnabled(); + } else { + EXPECT_TRUE(win_util::UserAccountControlIsEnabled()); + } +} + +TEST(BaseWinUtilTest, TestGetUserSidString) { + std::wstring user_sid; + EXPECT_TRUE(win_util::GetUserSidString(&user_sid)); + EXPECT_TRUE(!user_sid.empty()); +} + +TEST(BaseWinUtilTest, TestGetNonClientMetrics) { + NONCLIENTMETRICS metrics = {0}; + win_util::GetNonClientMetrics(&metrics); + EXPECT_TRUE(metrics.cbSize > 0); + EXPECT_TRUE(metrics.iScrollWidth > 0); + EXPECT_TRUE(metrics.iScrollHeight > 0); +}
\ No newline at end of file diff --git a/base/windows_message_list.h b/base/windows_message_list.h new file mode 100644 index 0000000..7a60fb0 --- /dev/null +++ b/base/windows_message_list.h @@ -0,0 +1,274 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// WARNING: DO NOT USE standard header file protection. +// This file may be include several times in its entirety. + +// This file contains a list of all messages supported by Windows as would be +// handled in a message loop. We only list the messages provided in +// <winuser.h>, and do not currently include (the otherwise undefined) +// #define WM_SYSTIMER 0x118 + +// By using various macro tricks, this list can be used to create pretty print +// functions for the messages. See message_loop.cc for an example. + +// Start list of Windows Messages given in <winuser.h> +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NULL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CREATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DESTROY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SIZE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ACTIVATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETFOCUS) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_KILLFOCUS) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ENABLE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETREDRAW) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETTEXT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETTEXT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETTEXTLENGTH) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PAINT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CLOSE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_QUERYENDSESSION) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_QUERYOPEN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ENDSESSION) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_QUIT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ERASEBKGND) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SYSCOLORCHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SHOWWINDOW) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_WININICHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETTINGCHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DEVMODECHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ACTIVATEAPP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_FONTCHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_TIMECHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CANCELMODE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETCURSOR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSEACTIVATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CHILDACTIVATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_QUEUESYNC) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETMINMAXINFO) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PAINTICON) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ICONERASEBKGND) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NEXTDLGCTL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SPOOLERSTATUS) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DRAWITEM) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MEASUREITEM) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DELETEITEM) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_VKEYTOITEM) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CHARTOITEM) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETFONT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETFONT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETHOTKEY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETHOTKEY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_QUERYDRAGICON) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_COMPAREITEM) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETOBJECT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_COMPACTING) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_COMMNOTIFY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_WINDOWPOSCHANGING) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_WINDOWPOSCHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_POWER) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_COPYDATA) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CANCELJOURNAL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NOTIFY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_INPUTLANGCHANGEREQUEST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_INPUTLANGCHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_TCARD) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_HELP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_USERCHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NOTIFYFORMAT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CONTEXTMENU) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_STYLECHANGING) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_STYLECHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DISPLAYCHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETICON) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SETICON) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCCREATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCDESTROY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCCALCSIZE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCHITTEST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCPAINT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCACTIVATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETDLGCODE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SYNCPAINT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCMOUSEMOVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCLBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCLBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCLBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCRBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCRBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCRBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCMBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCMBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCMBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCXBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCXBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCXBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_INPUT_DEVICE_CHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_INPUT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_KEYFIRST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_KEYDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_KEYUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CHAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DEADCHAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SYSKEYDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SYSKEYUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SYSCHAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SYSDEADCHAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_UNICHAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_KEYLAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_KEYLAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_STARTCOMPOSITION) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_ENDCOMPOSITION) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_COMPOSITION) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_KEYLAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_INITDIALOG) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_COMMAND) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SYSCOMMAND) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_TIMER) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_HSCROLL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_VSCROLL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_INITMENU) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_INITMENUPOPUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MENUSELECT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MENUCHAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ENTERIDLE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MENURBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MENUDRAG) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MENUGETOBJECT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_UNINITMENUPOPUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MENUCOMMAND) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CHANGEUISTATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_UPDATEUISTATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_QUERYUISTATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CTLCOLORMSGBOX) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CTLCOLOREDIT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CTLCOLORLISTBOX) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CTLCOLORBTN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CTLCOLORDLG) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CTLCOLORSCROLLBAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CTLCOLORSTATIC) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSEFIRST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSEMOVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_LBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_LBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_LBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_RBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_RBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_RBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSEWHEEL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_XBUTTONDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_XBUTTONUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_XBUTTONDBLCLK) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSEHWHEEL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSELAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSELAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSELAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSELAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PARENTNOTIFY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ENTERMENULOOP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_EXITMENULOOP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NEXTMENU) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SIZING) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CAPTURECHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOVING) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_POWERBROADCAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DEVICECHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDICREATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDIDESTROY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDIACTIVATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDIRESTORE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDINEXT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDIMAXIMIZE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDITILE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDICASCADE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDIICONARRANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDIGETACTIVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDISETMENU) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ENTERSIZEMOVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_EXITSIZEMOVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DROPFILES) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MDIREFRESHMENU) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_SETCONTEXT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_NOTIFY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_CONTROL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_COMPOSITIONFULL) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_SELECT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_CHAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_REQUEST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_KEYDOWN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_IME_KEYUP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSEHOVER) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_MOUSELEAVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCMOUSEHOVER) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_NCMOUSELEAVE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_WTSSESSION_CHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_TABLET_FIRST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_TABLET_LAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CUT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_COPY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PASTE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CLEAR) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_UNDO) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_RENDERFORMAT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_RENDERALLFORMATS) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DESTROYCLIPBOARD) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DRAWCLIPBOARD) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PAINTCLIPBOARD) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_VSCROLLCLIPBOARD) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_SIZECLIPBOARD) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_ASKCBFORMATNAME) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CHANGECBCHAIN) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_HSCROLLCLIPBOARD) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_QUERYNEWPALETTE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PALETTEISCHANGING) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PALETTECHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_HOTKEY) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PRINT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PRINTCLIENT) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_APPCOMMAND) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_THEMECHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_CLIPBOARDUPDATE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DWMCOMPOSITIONCHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DWMNCRENDERINGCHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DWMCOLORIZATIONCOLORCHANGED) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_DWMWINDOWMAXIMIZEDCHANGE) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_GETTITLEBARINFOEX) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_HANDHELDFIRST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_HANDHELDLAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_AFXFIRST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_AFXLAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PENWINFIRST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_PENWINLAST) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_APP) +A_NAMED_MESSAGE_FROM_WINUSER_H(WM_USER) +// End list of Windows Messages given in <winuser.h> diff --git a/base/wmi_util.cc b/base/wmi_util.cc new file mode 100644 index 0000000..e9159e8 --- /dev/null +++ b/base/wmi_util.cc @@ -0,0 +1,145 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <atlbase.h> + +#pragma comment(lib, "wbemuuid.lib") + +#include "base/wmi_util.h" + +bool WMIUtil::CreateLocalConnection(bool set_blanket, + IWbemServices** wmi_services) { + CComPtr<IWbemLocator> wmi_locator; + HRESULT hr = wmi_locator.CoCreateInstance(CLSID_WbemLocator, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(hr)) + return false; + + CComPtr<IWbemServices> wmi_services_r; + hr = wmi_locator->ConnectServer(CComBSTR(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, + 0, 0, &wmi_services_r); + if (FAILED(hr)) + return false; + + if (set_blanket) { + hr = ::CoSetProxyBlanket(wmi_services_r, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + NULL, + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE); + if (FAILED(hr)) + return false; + } + + *wmi_services = wmi_services_r.Detach(); + return true; +} + +bool WMIUtil::CreateClassMethodObject(IWbemServices* wmi_services, + const std::wstring& class_name, + const std::wstring& method_name, + IWbemClassObject** class_instance) { + // We attempt to instantiate a COM object that represents a WMI object plus + // a method rolled into one entity. + CComBSTR b_class_name(class_name.c_str()); + CComBSTR b_method_name(method_name.c_str()); + CComPtr<IWbemClassObject> class_object = NULL; + HRESULT hr; + hr = wmi_services->GetObject(b_class_name, 0, NULL, &class_object, NULL); + if (FAILED(hr)) + return false; + + CComPtr<IWbemClassObject> params_def = NULL; + hr = class_object->GetMethod(b_method_name, 0, ¶ms_def, NULL); + if (FAILED(hr)) + return false; + + if (NULL == params_def) { + // You hit this special case if the WMI class is not a CIM class. MSDN + // sometimes tells you this. Welcome to WMI hell. + return false; + } + + hr = params_def->SpawnInstance(0, class_instance); + return(SUCCEEDED(hr)); +} + +bool SetParameter(IWbemClassObject* class_method, + const std::wstring& parameter_name, VARIANT* parameter) { + HRESULT hr = class_method->Put(parameter_name.c_str(), 0, parameter, 0); + return SUCCEEDED(hr); +} + + +// The code in Launch() basically calls the Create Method of the Win32_Process +// CIM class is documented here: +// http://msdn2.microsoft.com/en-us/library/aa389388(VS.85).aspx + +bool WMIProcessUtil::Launch(const std::wstring& command_line, int* process_id) { + CComPtr<IWbemServices> wmi_local; + if (!WMIUtil::CreateLocalConnection(true, &wmi_local)) + return false; + + const wchar_t class_name[] = L"Win32_Process"; + const wchar_t method_name[] = L"Create"; + CComPtr<IWbemClassObject> process_create; + if (!WMIUtil::CreateClassMethodObject(wmi_local, class_name, method_name, + &process_create)) + return false; + + CComVariant b_command_line(command_line.c_str()); + if (!SetParameter(process_create, L"CommandLine", &b_command_line)) + return false; + + CComPtr<IWbemClassObject> out_params; + HRESULT hr = wmi_local->ExecMethod(CComBSTR(class_name), + CComBSTR(method_name), 0, NULL, + process_create, &out_params, NULL); + if (FAILED(hr)) + return false; + + CComVariant ret_value; + hr = out_params->Get(L"ReturnValue", 0, &ret_value, NULL, 0); + if (FAILED(hr) || (0 != ret_value.uintVal)) + return false; + + CComVariant pid; + hr = out_params->Get(L"ProcessId", 0, &pid, NULL, 0); + if (FAILED(hr) || (0 == pid.intVal)) + return false; + + if (process_id) + *process_id = pid.intVal; + + return true; +} diff --git a/base/wmi_util.h b/base/wmi_util.h new file mode 100644 index 0000000..737b740 --- /dev/null +++ b/base/wmi_util.h @@ -0,0 +1,98 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// WMI (Windows Management and Instrumentation) is a big, complex, COM-based +// API that can be used to perform all sorts of things. Sometimes is the best +// way to accomplish something under windows but its lack of an approachable +// C++ interface prevents its use. This collection of fucntions is a step in +// that direction. +// There are two classes; WMIUtil and WMIProcessUtil. The first +// one contain generic helpers and the second one contains the only +// functionality that is needed right now which is to use WMI to launch a +// process. +// To use any function on this header you must call CoInitialize or +// CoInitializeEx beforehand. +// +// For more information about WMI programming: +// http://msdn2.microsoft.com/en-us/library/aa384642(VS.85).aspx + +#ifndef BASE_WMI_UTIL_H__ +#define BASE_WMI_UTIL_H__ + +#include <string> +#include <wbemidl.h> + +class WMIUtil { + public: + // Creates an instance of the WMI service connected to the local computer and + // returns its COM interface. If 'set-blanket' is set to true, the basic COM + // security blanket is applied to the returned interface. This is almost + // always desirable unless you set the parameter to false and apply a custom + // COM security blanket. + // Returns true if succeeded and 'wmi_services': the pointer to the service. + // When done with the interface you must call Release(); + static bool CreateLocalConnection(bool set_blanket, + IWbemServices** wmi_services); + + // Creates a WMI method using from a WMI class named 'class_name' that + // contains a method named 'method_name'. Only WMI classes that are CIM + // classes can be created using this function. + // Returns true if succeeded and 'class_instance' returns a pointer to the + // WMI method that you can fill with parameter values using SetParameter. + // When done with the interface you must call Release(); + static bool CreateClassMethodObject(IWbemServices* wmi_services, + const std::wstring& class_name, + const std::wstring& method_name, + IWbemClassObject** class_instance); + + // Fills a single parameter given an instanced 'class_method'. Returns true + // if operation succeeded. When all the parameters are set the method can + // be executed using IWbemServices::ExecMethod(). + static bool SetParameter(IWbemClassObject* class_method, + const std::wstring& parameter_name, + VARIANT* parameter); +}; + +// This class contains functionality of the WMI class 'Win32_Process' +// more info: http://msdn2.microsoft.com/en-us/library/aa394372(VS.85).aspx +class WMIProcessUtil { + public: + // Creates a new process from 'command_line'. The advantage over CreateProcess + // is that it allows you to always break out from a Job object that the caller + // is attached to even if the Job object flags prevent that. + // Returns true and the process id in process_id if the process is launched + // successful. False otherwise. + // Note that a fully qualified path must be specified in most cases unless + // the program is not in the search path of winmgmt.exe. + // Processes created this way are children of wmiprvse.exe and run with the + // caller credentials. + static bool Launch(const std::wstring& command_line, int* process_id); +}; + +#endif // BASE_WMI_UTIL_H__ diff --git a/base/wmi_util_unittest.cc b/base/wmi_util_unittest.cc new file mode 100644 index 0000000..40853a4 --- /dev/null +++ b/base/wmi_util_unittest.cc @@ -0,0 +1,80 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/wmi_util.h" + +TEST(WMIUtilTest, TestLocalConnectionSecurityBlanket) { + ::CoInitialize(NULL); + IWbemServices* services = NULL; + EXPECT_TRUE(WMIUtil::CreateLocalConnection(true, &services)); + ASSERT_TRUE(NULL != services); + ULONG refs = services->Release(); + EXPECT_EQ(refs, 0); + ::CoUninitialize(); +} + +TEST(WMIUtilTest, TestLocalConnectionNoSecurityBlanket) { + ::CoInitialize(NULL); + IWbemServices* services = NULL; + EXPECT_TRUE(WMIUtil::CreateLocalConnection(false, &services)); + ASSERT_TRUE(NULL != services); + ULONG refs = services->Release(); + EXPECT_EQ(refs, 0); + ::CoUninitialize(); +} + +TEST(WMIUtilTest, TestCreateClassMethod) { + ::CoInitialize(NULL); + IWbemServices* wmi_services = NULL; + EXPECT_TRUE(WMIUtil::CreateLocalConnection(true, &wmi_services)); + ASSERT_TRUE(NULL != wmi_services); + IWbemClassObject* class_method = NULL; + EXPECT_TRUE(WMIUtil::CreateClassMethodObject(wmi_services, + L"Win32_ShortcutFile", + L"Rename", &class_method)); + ASSERT_TRUE(NULL != class_method); + ULONG refs = class_method->Release(); + EXPECT_EQ(refs, 0); + refs = wmi_services->Release(); + EXPECT_EQ(refs, 0); + ::CoUninitialize(); +} + +// Creates an instance of cmd which executes 'echo' and exits immediately. +TEST(WMIUtilTest, TestLaunchProcess) { + ::CoInitialize(NULL); + int pid = 0; + bool result = WMIProcessUtil::Launch(L"cmd.exe /c echo excelent!", &pid); + EXPECT_TRUE(result); + EXPECT_GT(pid, 0); + ::CoUninitialize(); +} diff --git a/base/word_iterator.cc b/base/word_iterator.cc new file mode 100644 index 0000000..1291630 --- /dev/null +++ b/base/word_iterator.cc @@ -0,0 +1,88 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/logging.h" +#include "base/word_iterator.h" +#include "unicode/ubrk.h" + +const int WordIterator::npos = -1; + +WordIterator::WordIterator(const std::wstring& str, BreakType break_type) + : iter_(NULL), + string_(str), + break_type_(break_type), + prev_(npos), + pos_(0) { +} + +WordIterator::~WordIterator() { + if (iter_) + ubrk_close(iter_); +} + +bool WordIterator::Init() { + UErrorCode status = U_ZERO_ERROR; + UBreakIteratorType break_type; + switch (break_type_) { + case BREAK_WORD: + break_type = UBRK_WORD; + break; + case BREAK_LINE: + break_type = UBRK_LINE; + break; + default: + NOTREACHED(); + break_type = UBRK_LINE; + } + iter_ = ubrk_open(break_type, NULL, + string_.data(), static_cast<int32_t>(string_.size()), + &status); + if (U_FAILURE(status)) { + NOTREACHED() << "ubrk_open failed"; + return false; + } + ubrk_first(iter_); // Move the iterator to the beginning of the string. + return true; +} + +bool WordIterator::Advance() { + prev_ = pos_; + const int32_t pos = ubrk_next(iter_); + if (pos == UBRK_DONE) { + pos_ = npos; + return false; + } else { + pos_ = static_cast<int>(pos); + return true; + } +} + +bool WordIterator::IsWord() const { + return (ubrk_getRuleStatus(iter_) != UBRK_WORD_NONE); +}
\ No newline at end of file diff --git a/base/word_iterator.h b/base/word_iterator.h new file mode 100644 index 0000000..fe86411 --- /dev/null +++ b/base/word_iterator.h @@ -0,0 +1,110 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_WORD_ITERATOR_H__ +#define BASE_WORD_ITERATOR_H__ + +#include "base/basictypes.h" + +// The WordIterator class iterates through the words and word breaks +// in a string. (In the string " foo bar! ", the word breaks are at the +// periods in ". .foo. .bar.!. .".) +// +// To extract the words from a string, move a WordIterator through the +// string and test whether IsWord() is true. E.g., +// WordIterator iter(str, WordIterator::BREAK_WORD); +// if (!iter.Init()) return false; +// while (iter.Advance()) { +// if (iter.IsWord()) { +// // region [iter.prev(),iter.pos()) contains a word. +// LOG(INFO) << "word: " << iter.GetWord(); +// } +// } + + +class WordIterator { + public: + enum BreakType { + BREAK_WORD, + BREAK_LINE + }; + + // Requires |str| to live as long as the WordIterator does. + WordIterator(const std::wstring& str, BreakType break_type); + ~WordIterator(); + + // Init() must be called before any of the iterators are valid. + // Returns false if ICU failed to initialize. + bool Init(); + + // Return the current break position within the string, + // or WordIterator::npos when done. + int pos() const { return pos_; } + // Return the value of pos() returned before Advance() was last called. + int prev() const { return prev_; } + + // A special position value indicating "end of string". + static const int npos; + + // Advance to the next break. Returns false if we've run past the end of + // the string. (Note that the very last "word 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(); + + // 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.) + bool IsWord() const; + + // Return the word between prev() and pos(). + // Advance() must have been called successfully at least once + // for pos() to have advanced to somewhere useful. + std::wstring GetWord() const { + DCHECK(prev_ >= 0 && pos_ >= 0); + return string_.substr(prev_, pos_ - prev_); + } + + private: + // ICU iterator. + void* iter_; + + // The string we're iterating over. + const std::wstring& string_; + + // The breaking style (word/line). + BreakType break_type_; + + // Previous and current iterator positions. + int prev_, pos_; + + DISALLOW_EVIL_CONSTRUCTORS(WordIterator); +}; + +#endif // BASE_WORD_ITERATOR_H__
\ No newline at end of file diff --git a/base/worker_pool.cc b/base/worker_pool.cc new file mode 100644 index 0000000..e70edad --- /dev/null +++ b/base/worker_pool.cc @@ -0,0 +1,57 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/worker_pool.h" + +#include "base/logging.h" + +namespace { + +DWORD CALLBACK WorkItemCallback(void* param) { + Task* task = static_cast<Task*>(param); + task->Run(); + WorkerPool::RecycleTask(task); + return 0; +} + +} // namespace + +bool WorkerPool::Run(Task* task, bool slow) { + ULONG flags = 0; + if (slow) + flags |= WT_EXECUTELONGFUNCTION; + + if (!QueueUserWorkItem(WorkItemCallback, task, flags)) { + DLOG(ERROR) << "QueueUserWorkItem failed: " << GetLastError(); + RecycleTask(task); + return false; + } + + return true; +} diff --git a/base/worker_pool.h b/base/worker_pool.h new file mode 100644 index 0000000..3b0039f --- /dev/null +++ b/base/worker_pool.h @@ -0,0 +1,50 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_WORKER_POOL_H_ +#define BASE_WORKER_POOL_H_ + +#include "base/task.h" + +// This is a facility that runs tasks that don't require a specific thread or +// a message loop. +class WorkerPool { + public: + // This function posts |task| to run on a worker thread. |slow| should be used + // for tasks that will take a long time to execute. |task| will be recycled + // even if the function fails. + static bool Run(Task* task, bool slow); + + // Recycles the task. + static void RecycleTask(Task* task) { + task->RecycleOrDelete(); + } +}; + +#endif // BASE_WORKER_POOL_H_ |