Update //base to chromium 9659b08ea5a34f889dc4166217f438095ddc10d2 This updates our copy of //base to be based off of commit 9659b08ea by performing the following commands: 1.) Check out //base at 9659b08ea 2.) Apply the //base portion of the following commits from mojo: 57ca940d8410a27fdf517b6194d7ecb0f29ecc2a c3b05c507e712f4ee65522e0a00e38c735c85244 d3482c6d25faabd04dffcea32d4dee18af74d600 18a6ce7215ece72e4359600852bb0cf5fd87339f a67383613f17083d918c31cf377932f5b43b91ab 0204e1a3a6679a0e634b66f661e23f424f044152 d423877725e8606e5ebac142e5c4b5c8c2dc4810 7a690c5c0344946ed74402d61d473502bc03ad77 cfeba3cce284f8a4d382337201d479768bb0eb77 df7f866ce3da1ef3c212cde977a6a77dc832ce22 57be778fd4d820b026165969352d7b40d476730b 34776a703d8cce41052e5a3867a23d8970fbb549 0f5eacecbe12aae15b114a8511ae718506431b37 c4ebd590b6e2749a4ab3f97fd49bbd3a4d510759 3.) Fix up the rest of the repo for //base API changes: *) StartsWith/EndsWith string functions are in base:: namespace and have slightly different options *) Tokenize replaced with base::SplitString *) MessageLoopProxy removed, use TaskRunner instead 4.) git cl format, gn format 5.) Delete OWNERS file import from //base/mac to //chrome/... This puts mojo's copy at base very close to parity with the flutter engine's copy. R=viettrungluu@chromium.org Review URL: https://codereview.chromium.org/1641513004 .
diff --git a/BUILD.gn b/BUILD.gn index eff163a..02d0bf9 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -39,7 +39,7 @@ "base_paths_win.h", ] - if (is_android || is_mac) { + if (is_android || is_mac || is_ios) { sources -= [ "base_paths_posix.cc" ] } @@ -83,6 +83,7 @@ "android/content_uri_utils.cc", "android/content_uri_utils.h", "android/cpu_features.cc", + "android/cxa_demangle_stub.cc", "android/event_log.cc", "android/event_log.h", "android/field_trial_list.cc", @@ -139,11 +140,8 @@ "atomic_ref_count.h", "atomic_sequence_num.h", "atomicops.h", - "atomicops_internals_gcc.h", "atomicops_internals_mac.h", "atomicops_internals_portable.h", - "atomicops_internals_x86_gcc.cc", - "atomicops_internals_x86_gcc.h", "atomicops_internals_x86_msvc.h", "auto_reset.h", "barrier_closure.cc", @@ -177,6 +175,7 @@ "containers/linked_list.h", "containers/mru_cache.h", "containers/scoped_ptr_hash_map.h", + "containers/scoped_ptr_map.h", "containers/small_map.h", "containers/stack_container.h", "cpu.cc", @@ -270,6 +269,9 @@ "mac/bind_objc_block.h", "mac/bundle_locations.h", "mac/bundle_locations.mm", + "mac/call_with_eh_frame.cc", + "mac/call_with_eh_frame.h", + "mac/call_with_eh_frame_asm.S", "mac/cocoa_protocols.h", "mac/dispatch_source_mach.cc", "mac/dispatch_source_mach.h", @@ -320,10 +322,8 @@ "message_loop/incoming_task_queue.h", "message_loop/message_loop.cc", "message_loop/message_loop.h", - "message_loop/message_loop_proxy.cc", - "message_loop/message_loop_proxy.h", - "message_loop/message_loop_proxy_impl.cc", - "message_loop/message_loop_proxy_impl.h", + "message_loop/message_loop_task_runner.cc", + "message_loop/message_loop_task_runner.h", "message_loop/message_pump.cc", "message_loop/message_pump.h", "message_loop/message_pump_android.cc", @@ -364,7 +364,6 @@ "pending_task.h", "pickle.cc", "pickle.h", - "port.h", "posix/eintr_wrapper.h", "posix/file_descriptor_shuffle.cc", "posix/global_descriptors.cc", @@ -427,6 +426,8 @@ "strings/latin1_string_conversions.h", "strings/nullable_string16.cc", "strings/nullable_string16.h", + "strings/pattern.cc", + "strings/pattern.h", "strings/safe_sprintf.cc", "strings/safe_sprintf.h", "strings/string16.cc", @@ -642,6 +643,8 @@ "sys_info_openbsd.cc", ] + data = [] + configs += [ ":base_implementation" ] deps = [ @@ -722,6 +725,11 @@ "sys_info.cc", "sys_info_posix.cc", ] + + # We build with a 10.9 min sdk version but lots of code in this directory is + # written to target a 10.6 min sdk version and thus depends on declarations + # marked as deprecated on 10.9+. Suppress these warnings for now. + cflags = [ "-Wno-deprecated-declarations" ] } else { # Remove NaCl stuff. sources -= [ @@ -742,6 +750,30 @@ "sha1_win.cc", ] + # Required for base/stack_trace_win.cc to symbolize correctly. + data += [ "$root_build_dir/dbghelp.dll" ] + + if (is_component_build) { + # Copy the VS runtime DLLs into the isolate so that they don't have to be + # preinstalled on the target machine. The debug runtimes have a "d" at + # the end. + if (is_debug) { + vcrt_suffix = "d" + } else { + vcrt_suffix = "" + } + + # These runtime files are copied to the output directory by the + # vs_toolchain script that runs as part of toolchain configuration. + data += [ + "$root_out_dir/msvcp120${vcrt_suffix}.dll", + "$root_out_dir/msvcr120${vcrt_suffix}.dll", + ] + if (is_asan) { + data += [ "//third_party/llvm-build/Release+Asserts/lib/clang/3.7.0/lib/windows/clang_rt.asan_dynamic-i386.dll" ] + } + } + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] @@ -758,17 +790,25 @@ } # Mac. - if (is_mac) { + if (is_mac || is_ios) { + # Common Desktop / iOS excludes sources -= [ "native_library_posix.cc", "strings/sys_string_conversions_posix.cc", "threading/platform_thread_internal_posix.cc", ] - # We build with a 10.9 min sdk version but lots of code in this directory is - # written to target a 10.6 min sdk version and thus depends on declarations - # marked as deprecated on 10.9+. Suppress these warnings for now. - cflags = [ "-Wno-deprecated-declarations" ] + if (is_asan) { + # TODO(GYP) hook up asan on Mac. GYP has this extra dylib: + #data += [ "$root_out_dir/libclang_rt.asan_osx_dynamic.dylib" ] + } + + if (is_ios) { + sources -= [ + "files/file_path_watcher_fsevents.cc", + "files/file_path_watcher_fsevents.h", + ] + } } else { # Non-Mac. sources -= [ @@ -781,6 +821,11 @@ # Linux. if (is_linux) { + if (is_asan || is_lsan || is_msan || is_tsan) { + # For llvm-sanitizer. + data += [ "//third_party/llvm-build/Release+Asserts/lib/libstdc++.so.6" ] + } + # TODO(brettw) this will need to be parameterized at some point. linux_configs = [] @@ -810,8 +855,60 @@ } } + # iOS + if (is_ios) { + set_sources_assignment_filter([]) + + sources += [ + "atomicops_internals_mac.h", + "base_paths_mac.h", + "base_paths_mac.mm", + "file_version_info_mac.h", + "file_version_info_mac.mm", + "files/file_util_mac.mm", + "mac/bundle_locations.h", + "mac/bundle_locations.mm", + "mac/call_with_eh_frame.cc", + "mac/call_with_eh_frame.h", + "mac/foundation_util.h", + "mac/foundation_util.mm", + "mac/mac_logging.cc", + "mac/mac_logging.h", + "mac/mach_logging.cc", + "mac/mach_logging.h", + "mac/objc_property_releaser.h", + "mac/objc_property_releaser.mm", + "mac/scoped_mach_port.cc", + "mac/scoped_mach_port.h", + "mac/scoped_mach_vm.cc", + "mac/scoped_mach_vm.h", + "mac/scoped_nsautorelease_pool.h", + "mac/scoped_nsautorelease_pool.mm", + "mac/scoped_nsobject.h", + "mac/scoped_objc_class_swizzler.h", + "mac/scoped_objc_class_swizzler.mm", + "message_loop/message_pump_mac.h", + "message_loop/message_pump_mac.mm", + "strings/sys_string_conversions_mac.mm", + "threading/platform_thread_mac.mm", + "time/time_mac.cc", + ] + + set_sources_assignment_filter(sources_assignment_filter) + } + + if (is_asan || is_lsan || is_msan || is_tsan) { + data += [ "//tools/valgrind/asan/" ] + if (is_win) { + data += + [ "//third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer.exe" ] + } else { + data += [ "//third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer" ] + } + } + configs += [ "//build/config/compiler:wexit_time_destructors" ] - if (is_android && !is_debug) { + if (!is_debug) { configs -= [ "//build/config/compiler:optimize" ] configs += [ "//build/config/compiler:optimize_max" ] } @@ -830,7 +927,7 @@ "win/pe_image.h", ] - if (is_android && !is_debug) { + if (!is_debug) { configs -= [ "//build/config/compiler:optimize" ] configs += [ "//build/config/compiler:optimize_max" ] } @@ -883,7 +980,7 @@ "//third_party/icu", ] - if (is_android && !is_debug) { + if (!is_debug) { configs -= [ "//build/config/compiler:optimize" ] configs += [ "//build/config/compiler:optimize_max" ] } @@ -995,7 +1092,7 @@ ":base", ] - if (is_android && !is_debug) { + if (!is_debug) { configs -= [ "//build/config/compiler:optimize" ] configs += [ "//build/config/compiler:optimize_max" ] } @@ -1056,6 +1153,15 @@ } } +# TODO(GYP): Delete this after we've converted everything to GN. +# The _run targets exist only for compatibility w/ GYP. +group("base_unittests_run") { + testonly = true + deps = [ + ":base_unittests", + ] +} + test("base_unittests") { sources = [ "android/application_status_listener_unittest.cc", @@ -1089,6 +1195,7 @@ "containers/linked_list_unittest.cc", "containers/mru_cache_unittest.cc", "containers/scoped_ptr_hash_map_unittest.cc", + "containers/scoped_ptr_map_unittest.cc", "containers/small_map_unittest.cc", "containers/stack_container_unittest.cc", "cpu_unittest.cc", @@ -1136,6 +1243,7 @@ "lazy_instance_unittest.cc", "logging_unittest.cc", "mac/bind_objc_block_unittest.mm", + "mac/call_with_eh_frame_unittest.mm", "mac/dispatch_source_mach_unittest.cc", "mac/foundation_util_unittest.mm", "mac/libdispatch_task_runner_unittest.cc", @@ -1159,8 +1267,7 @@ "memory/singleton_unittest.cc", "memory/weak_ptr_unittest.cc", "memory/weak_ptr_unittest.nc", - "message_loop/message_loop_proxy_impl_unittest.cc", - "message_loop/message_loop_proxy_unittest.cc", + "message_loop/message_loop_task_runner_unittest.cc", "message_loop/message_loop_unittest.cc", "message_loop/message_pump_io_ios_unittest.cc", "metrics/bucket_ranges_unittest.cc", @@ -1211,6 +1318,7 @@ "sha1_unittest.cc", "stl_util_unittest.cc", "strings/nullable_string16_unittest.cc", + "strings/pattern_unittest.cc", "strings/safe_sprintf_unittest.cc", "strings/string16_unittest.cc", "strings/string_number_conversions_unittest.cc", @@ -1308,9 +1416,6 @@ data = [ "test/data/", - - # TODO(dpranke): Remove when icu declares this directly. - "$root_out_dir/icudtl.dat", ] # Allow more direct string conversions on platforms with native utf8 @@ -1324,14 +1429,15 @@ ":base_java", ":base_java_unittest_support", ] + + # TODO(brettw) I think this should not be here, we should not be using + # isolate files. isolate_file = "base_unittests.isolate" } if (is_ios) { sources -= [ "process/memory_unittest.cc", - "process/memory_unittest_mac.h", - "process/memory_unittest_mac.mm", "process/process_unittest.cc", "process/process_util_unittest.cc", ] @@ -1371,6 +1477,15 @@ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] + + # Symbols for crashes when running tests on swarming. + if (symbol_level > 0) { + if (is_win) { + data += [ "$root_out_dir/base_unittests.exe.pdb" ] + } else if (is_mac) { + data += [ "$root_out_dir/base_unittests.dSYM/" ] + } + } } if (is_android) {
diff --git a/OWNERS b/OWNERS index 9890491..bcaa81b 100644 --- a/OWNERS +++ b/OWNERS
@@ -1,13 +1,9 @@ mark@chromium.org -darin@chromium.org thakis@chromium.org danakj@chromium.org rvargas@chromium.org thestig@chromium.org -# On extended leave. -willchan@chromium.org - # Chromium is a very mature project, most things that are generally useful are # already here, and that things not here aren't generally useful. # @@ -21,8 +17,9 @@ # writing the stuff you want to log in a regular logging statement, even # if it makes your calling code longer. Just add it to your own code. -per-file *.isolate=csharp@chromium.org per-file *.isolate=maruel@chromium.org +per-file *.isolate=tandrii@chromium.org +per-file *.isolate=vadimsh@chromium.org per-file security_unittest.cc=jln@chromium.org # For Android-specific changes:
diff --git a/allocator/BUILD.gn b/allocator/BUILD.gn index a07a356..c42de1a 100644 --- a/allocator/BUILD.gn +++ b/allocator/BUILD.gn
@@ -63,6 +63,17 @@ "$tcmalloc_dir/src/base/abort.cc", "$tcmalloc_dir/src/base/abort.h", "$tcmalloc_dir/src/base/arm_instruction_set_select.h", + "$tcmalloc_dir/src/base/atomicops-internals-arm-generic.h", + "$tcmalloc_dir/src/base/atomicops-internals-arm-v6plus.h", + "$tcmalloc_dir/src/base/atomicops-internals-linuxppc.h", + "$tcmalloc_dir/src/base/atomicops-internals-macosx.h", + "$tcmalloc_dir/src/base/atomicops-internals-windows.h", + "$tcmalloc_dir/src/base/atomicops-internals-x86.cc", + "$tcmalloc_dir/src/base/atomicops-internals-x86.h", + "$tcmalloc_dir/src/base/atomicops.h", + "$tcmalloc_dir/src/base/basictypes.h", + "$tcmalloc_dir/src/base/commandlineflags.h", + "$tcmalloc_dir/src/base/cycleclock.h", # We don't list dynamic_annotations.c since its copy is already # present in the dynamic_annotations target.
diff --git a/android/build_info.cc b/android/build_info.cc index 4d3cd55..93667ef 100644 --- a/android/build_info.cc +++ b/android/build_info.cc
@@ -48,20 +48,21 @@ model_(StrDupJString(Java_BuildInfo_getDeviceModel(env))), brand_(StrDupJString(Java_BuildInfo_getBrand(env))), android_build_id_(StrDupJString(Java_BuildInfo_getAndroidBuildId(env))), - android_build_fp_(StrDupJString( - Java_BuildInfo_getAndroidBuildFingerprint(env))), - package_version_code_(StrDupJString(Java_BuildInfo_getPackageVersionCode( - env, GetApplicationContext()))), - package_version_name_(StrDupJString(Java_BuildInfo_getPackageVersionName( - env, GetApplicationContext()))), - package_label_(StrDupJString(Java_BuildInfo_getPackageLabel( - env, GetApplicationContext()))), - package_name_(StrDupJString(Java_BuildInfo_getPackageName( - env, GetApplicationContext()))), + android_build_fp_( + StrDupJString(Java_BuildInfo_getAndroidBuildFingerprint(env))), + package_version_code_(StrDupJString( + Java_BuildInfo_getPackageVersionCode(env, GetApplicationContext()))), + package_version_name_(StrDupJString( + Java_BuildInfo_getPackageVersionName(env, GetApplicationContext()))), + package_label_(StrDupJString( + Java_BuildInfo_getPackageLabel(env, GetApplicationContext()))), + package_name_(StrDupJString( + Java_BuildInfo_getPackageName(env, GetApplicationContext()))), build_type_(StrDupJString(Java_BuildInfo_getBuildType(env))), sdk_int_(Java_BuildInfo_getSdkInt(env)), - java_exception_info_(NULL) { -} + has_language_apk_splits_( + Java_BuildInfo_hasLanguageApkSplits(env, GetApplicationContext())), + java_exception_info_(NULL) {} // static BuildInfo* BuildInfo::GetInstance() {
diff --git a/android/build_info.h b/android/build_info.h index 093b832..0d722b5 100644 --- a/android/build_info.h +++ b/android/build_info.h
@@ -96,6 +96,8 @@ return sdk_int_; } + int has_language_apk_splits() const { return has_language_apk_splits_; } + const char* java_exception_info() const { return java_exception_info_; } @@ -127,6 +129,7 @@ const char* const package_name_; const char* const build_type_; const int sdk_int_; + const bool has_language_apk_splits_; // This is set via set_java_exception_info, not at constructor time. const char* java_exception_info_;
diff --git a/android/cxa_demangle_stub.cc b/android/cxa_demangle_stub.cc new file mode 100644 index 0000000..b0d2b87 --- /dev/null +++ b/android/cxa_demangle_stub.cc
@@ -0,0 +1,19 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <unistd.h> + +// LLVM's demangler is large, and we have no need of it. Overriding it with +// our own stub version here stops a lot of code being pulled in from libc++. +// More here: +// https://llvm.org/svn/llvm-project/libcxxabi/trunk/src/cxa_demangle.cpp +extern "C" char* __cxa_demangle(const char* mangled_name, + char* buf, + size_t* n, + int* status) { + static const int kMemoryAllocFailure = -1; // LLVM's memory_alloc_failure. + if (status) + *status = kMemoryAllocFailure; + return nullptr; +}
diff --git a/android/java/src/org/chromium/base/BaseChromiumApplication.java b/android/java/src/org/chromium/base/BaseChromiumApplication.java index d7c7b05..8ec4afa 100644 --- a/android/java/src/org/chromium/base/BaseChromiumApplication.java +++ b/android/java/src/org/chromium/base/BaseChromiumApplication.java
@@ -8,7 +8,6 @@ import android.app.Application; import android.content.Context; import android.os.Bundle; -import android.view.KeyEvent; import android.view.Window; import java.lang.reflect.InvocationHandler; @@ -38,9 +37,6 @@ /** * Intercepts calls to an existing Window.Callback. Most invocations are passed on directly * to the composed Window.Callback but enables intercepting/manipulating others. - * - * This is used to relay window focus changes throughout the app and remedy a bug in the - * appcompat library. */ private class WindowCallbackProxy implements InvocationHandler { private final Window.Callback mCallback; @@ -57,9 +53,6 @@ && args[0] instanceof Boolean) { onWindowFocusChanged((boolean) args[0]); return null; - } else if (method.getName().equals("dispatchKeyEvent") && args.length == 1 - && args[0] instanceof KeyEvent) { - return dispatchKeyEvent((KeyEvent) args[0]); } else { try { return method.invoke(mCallback, args); @@ -85,15 +78,6 @@ listener.onWindowFocusChanged(mActivity, hasFocus); } } - - public boolean dispatchKeyEvent(KeyEvent event) { - // TODO(aurimas): remove this once AppCompatDelegateImpl no longer steals - // KEYCODE_MENU. (see b/20529185) - if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && mActivity.dispatchKeyEvent(event)) { - return true; - } - return mCallback.dispatchKeyEvent(event); - } } @Override public void onCreate() {
diff --git a/android/java/src/org/chromium/base/BuildInfo.java b/android/java/src/org/chromium/base/BuildInfo.java index 54f611d..1b91d39 100644 --- a/android/java/src/org/chromium/base/BuildInfo.java +++ b/android/java/src/org/chromium/base/BuildInfo.java
@@ -4,12 +4,14 @@ package org.chromium.base; +import android.annotation.TargetApi; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; +import android.text.TextUtils; import android.util.Log; /** @@ -122,4 +124,35 @@ public static int getSdkInt() { return Build.VERSION.SDK_INT; } + + /** + * @return Whether the Android build is M or later. + */ + public static boolean isMncOrLater() { + // TODO(bauerb): Update this once the SDK is updated. + return Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1 + || TextUtils.equals("MNC", Build.VERSION.CODENAME); + } + + private static boolean isLanguageSplit(String splitName) { + // Names look like "config.XX". + return splitName.length() == 9 && splitName.startsWith("config."); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @CalledByNative + public static boolean hasLanguageApkSplits(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return false; + } + PackageInfo packageInfo = PackageUtils.getOwnPackageInfo(context); + if (packageInfo.splitNames != null) { + for (int i = 0; i < packageInfo.splitNames.length; ++i) { + if (isLanguageSplit(packageInfo.splitNames[i])) { + return true; + } + } + } + return false; + } }
diff --git a/android/java/src/org/chromium/base/CollectionUtil.java b/android/java/src/org/chromium/base/CollectionUtil.java index 4a4c754..2672d65 100644 --- a/android/java/src/org/chromium/base/CollectionUtil.java +++ b/android/java/src/org/chromium/base/CollectionUtil.java
@@ -31,6 +31,7 @@ return list; } + @VisibleForTesting public static <E> ArrayList<E> newArrayList(Iterable<E> iterable) { ArrayList<E> list = new ArrayList<E>(); for (E element : iterable) {
diff --git a/android/java/src/org/chromium/base/Log.java b/android/java/src/org/chromium/base/Log.java index c83cfe7..3b46cdc 100644 --- a/android/java/src/org/chromium/base/Log.java +++ b/android/java/src/org/chromium/base/Log.java
@@ -4,9 +4,7 @@ package org.chromium.base; -import android.text.TextUtils; - -import org.chromium.base.annotations.NoSideEffects; +import org.chromium.base.annotations.RemovableInRelease; import java.util.Locale; @@ -62,27 +60,6 @@ return "[" + getCallOrigin() + "] " + formatLog(messageTemplate, params); } - /** - * Returns a full tag for the provided group tag. Full tags longer than 23 characters - * will cause a runtime exception. - * - * @param groupTag {@code null} and empty string are allowed. - * - * @see android.util.Log#isLoggable(String, int) - * @throws IllegalArgumentException if the tag is too long. - * @deprecated Directly use a string (e.g. "cr.Tag") in your class. See http://crbug.com/485772 - */ - @Deprecated - public static String makeTag(String groupTag) { - if (TextUtils.isEmpty(groupTag)) return "cr"; - String tag = "cr." + groupTag; - if (tag.length() > 23) { - throw new IllegalArgumentException( - "The full tag (" + tag + ") is longer than 23 characters."); - } - return tag; - } - /** Convenience function, forwards to {@link android.util.Log#isLoggable(String, int)}. */ public static boolean isLoggable(String tag, int level) { return android.util.Log.isLoggable(tag, level); @@ -93,7 +70,7 @@ * * For optimization purposes, only the fixed parameters versions are visible. If you need more * than 7 parameters, consider building your log message using a function annotated with - * {@link NoSideEffects}. + * {@link RemovableInRelease}. * * @param tag Used to identify the source of a log message. * @param messageTemplate The message you would like logged. It is to be specified as a format @@ -102,7 +79,7 @@ * one is a {@link Throwable}, its trace will be printed. */ private static void verbose(String tag, String messageTemplate, Object... args) { - if (android.util.Log.isLoggable(tag, android.util.Log.VERBOSE)) { + if (Log.isLoggable(tag, Log.VERBOSE)) { String message = formatLogWithStack(messageTemplate, args); Throwable tr = getThrowableToLog(args); if (tr != null) { @@ -113,46 +90,62 @@ } } - /** Sends a {@link android.util.Log#VERBOSE} log message. 0 arg version. */ + /** Sends a {@link android.util.Log#VERBOSE} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting public static void v(String tag, String message) { verbose(tag, message); } /** Sends a {@link android.util.Log#VERBOSE} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting public static void v(String tag, String messageTemplate, Object arg1) { verbose(tag, messageTemplate, arg1); } /** Sends a {@link android.util.Log#VERBOSE} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting public static void v(String tag, String messageTemplate, Object arg1, Object arg2) { verbose(tag, messageTemplate, arg1, arg2); } /** Sends a {@link android.util.Log#VERBOSE} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting public static void v( String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { verbose(tag, messageTemplate, arg1, arg2, arg3); } /** Sends a {@link android.util.Log#VERBOSE} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4) { verbose(tag, messageTemplate, arg1, arg2, arg3, arg4); } /** Sends a {@link android.util.Log#VERBOSE} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); } /** Sends a {@link android.util.Log#VERBOSE} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) { verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); } /** Sends a {@link android.util.Log#VERBOSE} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) { verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); @@ -163,7 +156,7 @@ * * For optimization purposes, only the fixed parameters versions are visible. If you need more * than 7 parameters, consider building your log message using a function annotated with - * {@link NoSideEffects}. + * {@link RemovableInRelease}. * * @param tag Used to identify the source of a log message. * @param messageTemplate The message you would like logged. It is to be specified as a format @@ -172,7 +165,7 @@ * one is a {@link Throwable}, its trace will be printed. */ private static void debug(String tag, String messageTemplate, Object... args) { - if (android.util.Log.isLoggable(tag, android.util.Log.VERBOSE)) { + if (isLoggable(tag, Log.DEBUG)) { String message = formatLogWithStack(messageTemplate, args); Throwable tr = getThrowableToLog(args); if (tr != null) { @@ -183,44 +176,60 @@ } } - /** Sends a {@link android.util.Log#DEBUG} log message. 0 arg version. */ + /** Sends a {@link android.util.Log#DEBUG} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting public static void d(String tag, String message) { debug(tag, message); } /** Sends a {@link android.util.Log#DEBUG} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting public static void d(String tag, String messageTemplate, Object arg1) { debug(tag, messageTemplate, arg1); } /** Sends a {@link android.util.Log#DEBUG} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting public static void d(String tag, String messageTemplate, Object arg1, Object arg2) { debug(tag, messageTemplate, arg1, arg2); } /** Sends a {@link android.util.Log#DEBUG} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting public static void d( String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { debug(tag, messageTemplate, arg1, arg2, arg3); } /** Sends a {@link android.util.Log#DEBUG} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4) { debug(tag, messageTemplate, arg1, arg2, arg3, arg4); } /** Sends a {@link android.util.Log#DEBUG} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); } /** Sends a {@link android.util.Log#DEBUG} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) { debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); } /** Sends a {@link android.util.Log#DEBUG} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) { debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); @@ -235,8 +244,9 @@ * @param args Arguments referenced by the format specifiers in the format string. If the last * one is a {@link Throwable}, its trace will be printed. */ + @VisibleForTesting public static void i(String tag, String messageTemplate, Object... args) { - if (android.util.Log.isLoggable(tag, android.util.Log.INFO)) { + if (Log.isLoggable(tag, Log.INFO)) { String message = formatLog(messageTemplate, args); Throwable tr = getThrowableToLog(args); if (tr != null) { @@ -256,8 +266,9 @@ * @param args Arguments referenced by the format specifiers in the format string. If the last * one is a {@link Throwable}, its trace will be printed. */ + @VisibleForTesting public static void w(String tag, String messageTemplate, Object... args) { - if (android.util.Log.isLoggable(tag, android.util.Log.WARN)) { + if (Log.isLoggable(tag, Log.WARN)) { String message = formatLog(messageTemplate, args); Throwable tr = getThrowableToLog(args); if (tr != null) { @@ -277,8 +288,9 @@ * @param args Arguments referenced by the format specifiers in the format string. If the last * one is a {@link Throwable}, its trace will be printed. */ + @VisibleForTesting public static void e(String tag, String messageTemplate, Object... args) { - if (android.util.Log.isLoggable(tag, android.util.Log.ERROR)) { + if (Log.isLoggable(tag, Log.ERROR)) { String message = formatLog(messageTemplate, args); Throwable tr = getThrowableToLog(args); if (tr != null) { @@ -302,8 +314,9 @@ * @param args Arguments referenced by the format specifiers in the format string. If the last * one is a {@link Throwable}, its trace will be printed. */ + @VisibleForTesting public static void wtf(String tag, String messageTemplate, Object... args) { - if (android.util.Log.isLoggable(tag, android.util.Log.ERROR)) { + if (Log.isLoggable(tag, Log.ASSERT)) { String message = formatLog(messageTemplate, args); Throwable tr = getThrowableToLog(args); if (tr != null) {
diff --git a/android/java/src/org/chromium/base/MemoryPressureListener.java b/android/java/src/org/chromium/base/MemoryPressureListener.java index e7c2030..7979287 100644 --- a/android/java/src/org/chromium/base/MemoryPressureListener.java +++ b/android/java/src/org/chromium/base/MemoryPressureListener.java
@@ -72,10 +72,10 @@ } else if (ACTION_TRIM_MEMORY.equals(action)) { simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_COMPLETE); } else if (ACTION_TRIM_MEMORY_RUNNING_CRITICAL.equals(action)) { - simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_MODERATE); - } else if (ACTION_TRIM_MEMORY_MODERATE.equals(action)) { simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL); + } else if (ACTION_TRIM_MEMORY_MODERATE.equals(action)) { + simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_MODERATE); } else { return false; }
diff --git a/android/java/src/org/chromium/base/PathUtils.java b/android/java/src/org/chromium/base/PathUtils.java index e46fc30..77affe1 100644 --- a/android/java/src/org/chromium/base/PathUtils.java +++ b/android/java/src/org/chromium/base/PathUtils.java
@@ -9,12 +9,14 @@ import android.os.AsyncTask; import android.os.Environment; +import java.io.File; import java.util.concurrent.ExecutionException; /** * This class provides the path related methods for the native library. */ public abstract class PathUtils { + private static final String THUMBNAIL_DIRECTORY = "textures"; private static final int DATA_DIRECTORY = 0; private static final int DATABASE_DIRECTORY = 1; @@ -22,6 +24,8 @@ private static final int NUM_DIRECTORIES = 3; private static AsyncTask<String, Void, String[]> sDirPathFetchTask; + private static File sThumbnailDirectory; + // Prevent instantiation. private PathUtils() {} @@ -91,6 +95,18 @@ return getDirectoryPath(CACHE_DIRECTORY); } + public static File getThumbnailCacheDirectory(Context appContext) { + if (sThumbnailDirectory == null) { + sThumbnailDirectory = appContext.getDir(THUMBNAIL_DIRECTORY, Context.MODE_PRIVATE); + } + return sThumbnailDirectory; + } + + @CalledByNative + public static String getThumbnailCacheDirectoryPath(Context appContext) { + return getThumbnailCacheDirectory(appContext).getAbsolutePath(); + } + /** * @return the public downloads directory. */
diff --git a/android/java/src/org/chromium/base/README_logging.md b/android/java/src/org/chromium/base/README_logging.md index a795b6b..6104bf0 100644 --- a/android/java/src/org/chromium/base/README_logging.md +++ b/android/java/src/org/chromium/base/README_logging.md
@@ -43,6 +43,20 @@ Level here is either `VERBOSE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `ASSERT`, or `SUPPRESS` By default, the level for all tags is `INFO`. +### An exception trace is printed when the exception is the last parameter ### + +As with `java.util.Log`, putting a throwable as last parameter will dump the corresponding stack +trace: + + Log.i(TAG, "An error happened: %s", e) + + I/cr.YourModuleTag: ( 999): An error happened: This is the exception's message + I/cr.YourModuleTag: ( 999): java.lang.Exception: This is the exception's message + I/cr.YourModuleTag: ( 999): at foo.bar.MyClass.test(MyClass.java:42) + I/cr.YourModuleTag: ( 999): ... + +Having the exception as last parameter doesn't prevent it from being used for string formatting. + ### Logging Best Practices #### Rule #1: Never log PII (Personal Identification Information): @@ -111,7 +125,7 @@ while the previous method can enable or disable debugging for a whole feature without changing any source file. -- Annotate debug functions with the `@NoSideEffects` annotation. +- Annotate debug functions with the `@RemovableInRelease` annotation. That annotation tells Proguard to assume that a given function has no side effects, and is called only for its returned value. If this value is unused, the call will be removed. If the @@ -121,7 +135,7 @@ /* If that function is only used in Log.d calls, proguard should completely remove it from * the release builds. */ - @NoSideEffects + @RemovableInRelease private static String getSomeDebugLogString(Thing[] things) { /* Still needs to be guarded to avoid impacting debug builds, or in case it's used for * some other log levels. But at least it is done only once, inside the function. */
diff --git a/android/java/src/org/chromium/base/ResourceExtractor.java b/android/java/src/org/chromium/base/ResourceExtractor.java index d44f2fc..030dcf9 100644 --- a/android/java/src/org/chromium/base/ResourceExtractor.java +++ b/android/java/src/org/chromium/base/ResourceExtractor.java
@@ -6,18 +6,17 @@ import android.annotation.TargetApi; import android.content.Context; -import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.res.AssetManager; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Trace; -import android.preference.PreferenceManager; import android.util.Log; +import org.chromium.base.annotations.SuppressFBWarnings; + import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; @@ -28,7 +27,6 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; -import java.util.regex.Pattern; /** * Handles extracting the necessary resources bundled in an APK and moving them to a location on @@ -37,23 +35,25 @@ public class ResourceExtractor { private static final String LOGTAG = "ResourceExtractor"; - private static final String LAST_LANGUAGE = "Last language"; - private static final String PAK_FILENAMES_LEGACY_NOREUSE = "Pak filenames"; private static final String ICU_DATA_FILENAME = "icudtl.dat"; private static final String V8_NATIVES_DATA_FILENAME = "natives_blob.bin"; private static final String V8_SNAPSHOT_DATA_FILENAME = "snapshot_blob.bin"; - private static String[] sMandatoryPaks = null; + private static ResourceEntry[] sResourcesToExtract = new ResourceEntry[0]; - // By default, we attempt to extract a pak file for the users - // current device locale. Use setExtractImplicitLocale() to - // change this behavior. - private static boolean sExtractImplicitLocalePak = true; + /** + * Holds information about a res/raw file (e.g. locale .pak files). + */ + public static final class ResourceEntry { + public final int resourceId; + public final String pathWithinApk; + public final String extractedFileName; - private static boolean isAppDataFile(String file) { - return ICU_DATA_FILENAME.equals(file) - || V8_NATIVES_DATA_FILENAME.equals(file) - || V8_SNAPSHOT_DATA_FILENAME.equals(file); + public ResourceEntry(int resourceId, String pathWithinApk, String extractedFileName) { + this.resourceId = resourceId; + this.pathWithinApk = pathWithinApk; + this.extractedFileName = extractedFileName; + } } private class ExtractTask extends AsyncTask<Void, Void, Void> { @@ -61,12 +61,38 @@ private final List<Runnable> mCompletionCallbacks = new ArrayList<Runnable>(); - public ExtractTask() { + private void extractResourceHelper(InputStream is, File outFile, byte[] buffer) + throws IOException { + OutputStream os = null; + try { + os = new FileOutputStream(outFile); + Log.i(LOGTAG, "Extracting resource " + outFile); + + int count = 0; + while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) { + os.write(buffer, 0, count); + } + os.flush(); + + // Ensure something reasonable was written. + if (outFile.length() == 0) { + throw new IOException(outFile + " extracted with 0 length!"); + } + } finally { + try { + if (is != null) { + is.close(); + } + } finally { + if (os != null) { + os.close(); + } + } + } } private void doInBackgroundImpl() { final File outputDir = getOutputDir(); - final File appDataDir = getAppDataDir(); if (!outputDir.exists() && !outputDir.mkdirs()) { Log.e(LOGTAG, "Unable to create pak resources directory!"); return; @@ -83,97 +109,22 @@ deleteFiles(); } - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - String currentLocale = LocaleUtils.getDefaultLocale(); - String currentLanguage = currentLocale.split("-", 2)[0]; - // If everything we need is already there (and the locale hasn't - // changed), quick exit. - if (prefs.getString(LAST_LANGUAGE, "").equals(currentLanguage)) { - boolean filesPresent = true; - for (String file : sMandatoryPaks) { - File directory = isAppDataFile(file) ? appDataDir : outputDir; - if (!new File(directory, file).exists()) { - filesPresent = false; - break; - } - } - if (filesPresent) return; - } else { - prefs.edit().putString(LAST_LANGUAGE, currentLanguage).apply(); - } - - StringBuilder p = new StringBuilder(); - for (String mandatoryPak : sMandatoryPaks) { - if (p.length() > 0) p.append('|'); - p.append("\\Q" + mandatoryPak + "\\E"); - } - - if (sExtractImplicitLocalePak) { - if (p.length() > 0) p.append('|'); - // As well as the minimum required set of .paks above, we'll - // also add all .paks that we have for the user's currently - // selected language. - p.append(currentLanguage); - p.append("(-\\w+)?\\.pak"); - } - - Pattern paksToInstall = Pattern.compile(p.toString()); - - AssetManager manager = mContext.getResources().getAssets(); beginTraceSection("WalkAssets"); + byte[] buffer = new byte[BUFFER_SIZE]; try { - // Loop through every asset file that we have in the APK, and look for the - // ones that we need to extract by trying to match the Patterns that we - // created above. - byte[] buffer = null; - String[] files = manager.list(""); - for (String file : files) { - if (!paksToInstall.matcher(file).matches()) { - continue; - } - File output = new File(isAppDataFile(file) ? appDataDir : outputDir, file); + // Extract all files that don't already exist. + for (ResourceEntry entry : sResourcesToExtract) { + File output = new File(outputDir, entry.extractedFileName); if (output.exists()) { continue; } - - InputStream is = null; - OutputStream os = null; beginTraceSection("ExtractResource"); + InputStream inputStream = + mContext.getResources().openRawResource(entry.resourceId); try { - is = manager.open(file); - os = new FileOutputStream(output); - Log.i(LOGTAG, "Extracting resource " + file); - if (buffer == null) { - buffer = new byte[BUFFER_SIZE]; - } - - int count = 0; - while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) { - os.write(buffer, 0, count); - } - os.flush(); - - // Ensure something reasonable was written. - if (output.length() == 0) { - throw new IOException(file + " extracted with 0 length!"); - } - - if (isAppDataFile(file)) { - // icu and V8 data need to be accessed by a renderer - // process. - output.setReadable(true, false); - } + extractResourceHelper(inputStream, output, buffer); } finally { - try { - if (is != null) { - is.close(); - } - } finally { - if (os != null) { - os.close(); - } - endTraceSection(); // ExtractResource - } + endTraceSection(); // ExtractResource } } } catch (IOException e) { @@ -303,53 +254,19 @@ } /** - * Specifies the .pak files that should be extracted from the APK's asset resources directory - * and moved to {@link #getOutputDirFromContext(Context)}. - * @param mandatoryPaks The list of pak files to be loaded. If no pak files are - * required, pass a single empty string. + * Specifies the files that should be extracted from the APK. + * and moved to {@link #getOutputDir()}. */ - public static void setMandatoryPaksToExtract(String... mandatoryPaks) { + @SuppressFBWarnings("EI_EXPOSE_STATIC_REP2") + public static void setResourcesToExtract(ResourceEntry[] entries) { assert (sInstance == null || sInstance.mExtractTask == null) : "Must be called before startExtractingResources is called"; - sMandatoryPaks = mandatoryPaks; - + sResourcesToExtract = entries; } - /** - * By default the ResourceExtractor will attempt to extract a pak resource for the users - * currently specified locale. This behavior can be changed with this function and is - * only needed by tests. - * @param extract False if we should not attempt to extract a pak file for - * the users currently selected locale and try to extract only the - * pak files specified in sMandatoryPaks. - */ - @VisibleForTesting - public static void setExtractImplicitLocaleForTesting(boolean extract) { - assert (sInstance == null || sInstance.mExtractTask == null) - : "Must be called before startExtractingResources is called"; - sExtractImplicitLocalePak = extract; - } - - /** - * Marks all the 'pak' resources, packaged as assets, for extraction during - * running the tests. - */ - @VisibleForTesting - public void setExtractAllPaksAndV8SnapshotForTesting() { - List<String> pakAndSnapshotFileAssets = new ArrayList<String>(); - AssetManager manager = mContext.getResources().getAssets(); - try { - String[] files = manager.list(""); - for (String file : files) { - if (file.endsWith(".pak")) pakAndSnapshotFileAssets.add(file); - } - } catch (IOException e) { - Log.w(LOGTAG, "Exception while accessing assets: " + e.getMessage(), e); - } - pakAndSnapshotFileAssets.add("natives_blob.bin"); - pakAndSnapshotFileAssets.add("snapshot_blob.bin"); - setMandatoryPaksToExtract(pakAndSnapshotFileAssets.toArray( - new String[pakAndSnapshotFileAssets.size()])); + // TODO(agrieve): Delete this method ones all usages of it are updated. + public static void setMandatoryPaksToExtract(String... paths) { + assert paths.length == 1 && "".equals(paths[0]); } private ResourceExtractor(Context context) { @@ -421,6 +338,10 @@ return; } + // If a previous release extracted resources, and the current release does not, + // deleteFiles() will not run and some files will be left. This currently + // can happen for ContentShell, but not for Chrome proper, since we always extract + // locale pak files. if (shouldSkipPakExtraction()) { return; } @@ -473,12 +394,9 @@ } /** - * Pak extraction not necessarily required by the embedder; we allow them to skip - * this process if they call setMandatoryPaksToExtract with a single empty String. + * Pak extraction not necessarily required by the embedder. */ private static boolean shouldSkipPakExtraction() { - // Must call setMandatoryPaksToExtract before beginning resource extraction. - assert sMandatoryPaks != null; - return sMandatoryPaks.length == 1 && "".equals(sMandatoryPaks[0]); + return sResourcesToExtract.length == 0; } }
diff --git a/android/java/src/org/chromium/base/StreamUtil.java b/android/java/src/org/chromium/base/StreamUtil.java new file mode 100644 index 0000000..f8cbfee --- /dev/null +++ b/android/java/src/org/chromium/base/StreamUtil.java
@@ -0,0 +1,28 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Helper methods to deal with stream related tasks. + */ +public class StreamUtil { + /** + * Handle closing a {@link java.io.Closeable} via {@link java.io.Closeable#close()} and catch + * the potentially thrown {@link java.io.IOException}. + * @param closeable The Closeable to be closed. + */ + public static void closeQuietly(Closeable closeable) { + if (closeable == null) return; + + try { + closeable.close(); + } catch (IOException ex) { + // Ignore the exception on close. + } + } +}
diff --git a/android/java/src/org/chromium/base/ThreadUtils.java b/android/java/src/org/chromium/base/ThreadUtils.java index c0b9172..647e33e 100644 --- a/android/java/src/org/chromium/base/ThreadUtils.java +++ b/android/java/src/org/chromium/base/ThreadUtils.java
@@ -29,6 +29,7 @@ } } + @VisibleForTesting public static void setUiThread(Looper looper) { synchronized (sLock) { if (sUiThreadHandler != null && sUiThreadHandler.getLooper() != looper) {
diff --git a/android/java/src/org/chromium/base/TraceEvent.java b/android/java/src/org/chromium/base/TraceEvent.java index 3d3b11a..9ace4a1 100644 --- a/android/java/src/org/chromium/base/TraceEvent.java +++ b/android/java/src/org/chromium/base/TraceEvent.java
@@ -20,6 +20,7 @@ public class TraceEvent { private static volatile boolean sEnabled = false; + private static volatile boolean sATraceEnabled = false; // True when taking an Android systrace. private static class BasicLooperMonitor implements Printer { @Override @@ -176,6 +177,8 @@ @CalledByNative public static void setEnabled(boolean enabled) { sEnabled = enabled; + // Android M+ systrace logs this on its own. Only log it if not writing to Android systrace. + if (sATraceEnabled) return; ThreadUtils.getUiThreadLooper().setMessageLogging( enabled ? LooperMonitorHolder.sInstance : null); } @@ -186,10 +189,15 @@ * systrace, this is for WebView only. */ public static void setATraceEnabled(boolean enabled) { - if (sEnabled == enabled) return; + if (sATraceEnabled == enabled) return; + sATraceEnabled = enabled; if (enabled) { + // Calls TraceEvent.setEnabled(true) via + // TraceLog::EnabledStateObserver::OnTraceLogEnabled nativeStartATrace(); } else { + // Calls TraceEvent.setEnabled(false) via + // TraceLog::EnabledStateObserver::OnTraceLogDisabled nativeStopATrace(); } }
diff --git a/android/java/src/org/chromium/base/annotations/NoSideEffects.java b/android/java/src/org/chromium/base/annotations/NoSideEffects.java deleted file mode 100644 index 803c3f9..0000000 --- a/android/java/src/org/chromium/base/annotations/NoSideEffects.java +++ /dev/null
@@ -1,17 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.base.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -/** - * Annotation used to indicate to proguard methods that have no side effects and can be - * safely removed if their return value is not used. This is to be used with - * {@link org.chromium.base.Log}'s method, that can also be removed by proguard. That way - * expensive calls can be left in debug builds but removed in release. - */ -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) -public @interface NoSideEffects {}
diff --git a/android/java/src/org/chromium/base/annotations/RemovableInRelease.java b/android/java/src/org/chromium/base/annotations/RemovableInRelease.java new file mode 100644 index 0000000..2191334 --- /dev/null +++ b/android/java/src/org/chromium/base/annotations/RemovableInRelease.java
@@ -0,0 +1,18 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * The annotated function can be removed in release builds. + * + * Calls to this function will be removed if its return value is not used. If all calls are removed, + * the function definition itself will be candidate for removal. + * It works by indicating to Proguard that the function has no side effects. + */ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface RemovableInRelease {}
diff --git a/android/java/src/org/chromium/base/library_loader/LegacyLinker.java b/android/java/src/org/chromium/base/library_loader/LegacyLinker.java new file mode 100644 index 0000000..81ac754 --- /dev/null +++ b/android/java/src/org/chromium/base/library_loader/LegacyLinker.java
@@ -0,0 +1,736 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.library_loader; + +import android.os.Bundle; +import android.os.Parcel; + +import org.chromium.base.CalledByNative; +import org.chromium.base.Log; +import org.chromium.base.SysUtils; +import org.chromium.base.ThreadUtils; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import javax.annotation.Nullable; + +/* + * For more, see Technical note, Security considerations, and the explanation + * of how this class is supposed to be used in Linker.java. + */ + +/** + * Provides a concrete implementation of the Chromium Linker. + * + * This Linker implementation uses the crazy linker to map and then run Chrome + * for Android. + * + * For more on the operations performed by the Linker, see {@link Linker}. + */ +class LegacyLinker extends Linker { + // Log tag for this class. + private static final String TAG = "cr.library_loader"; + + // Name of the library that contains our JNI code. + private static final String LINKER_JNI_LIBRARY = "chromium_android_linker"; + + // Becomes true after linker initialization. + private boolean mInitialized = false; + + // Set to true to indicate that the system supports safe sharing of RELRO sections. + private boolean mRelroSharingSupported = false; + + // Set to true if this runs in the browser process. Disabled by initServiceProcess(). + // TODO(petrcermak): This flag can be incorrectly set to false (even though this might run in + // the browser process) on low-memory devices. + private boolean mInBrowserProcess = true; + + // Becomes true to indicate this process needs to wait for a shared RELRO in + // finishLibraryLoad(). + private boolean mWaitForSharedRelros = false; + + // Becomes true when initialization determines that the browser process can use the + // shared RELRO. + private boolean mBrowserUsesSharedRelro = false; + + // The map of all RELRO sections either created or used in this process. + private Bundle mSharedRelros = null; + + // Current common random base load address. + private long mBaseLoadAddress = 0; + + // Current fixed-location load address for the next library called by loadLibrary(). + private long mCurrentLoadAddress = 0; + + // Becomes true once prepareLibraryLoad() has been called. + private boolean mPrepareLibraryLoadCalled = false; + + // The map of libraries that are currently loaded in this process. + protected HashMap<String, LibInfo> mLoadedLibraries = null; + + // Used internally to initialize the linker's static data. Assume lock is held. + private void ensureInitializedLocked() { + assert Thread.holdsLock(mLock); + + if (mInitialized) { + return; + } + + mRelroSharingSupported = false; + if (NativeLibraries.sUseLinker) { + if (DEBUG) { + Log.i(TAG, "Loading lib" + LINKER_JNI_LIBRARY + ".so"); + } + try { + System.loadLibrary(LINKER_JNI_LIBRARY); + } catch (UnsatisfiedLinkError e) { + // In a component build, the ".cr" suffix is added to each library name. + Log.w(TAG, "Couldn't load lib" + LINKER_JNI_LIBRARY + ".so, " + + "trying lib" + LINKER_JNI_LIBRARY + ".cr.so"); + System.loadLibrary(LINKER_JNI_LIBRARY + ".cr"); + } + mRelroSharingSupported = nativeCanUseSharedRelro(); + if (!mRelroSharingSupported) { + Log.w(TAG, "This system cannot safely share RELRO sections"); + } else { + if (DEBUG) { + Log.i(TAG, "This system supports safe shared RELRO sections"); + } + } + + if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT) { + if (SysUtils.isLowEndDevice()) { + mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_LOW; + } else { + mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_NORMAL; + } + } + + switch (BROWSER_SHARED_RELRO_CONFIG) { + case BROWSER_SHARED_RELRO_CONFIG_NEVER: + mBrowserUsesSharedRelro = false; + break; + case BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY: + if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) { + mBrowserUsesSharedRelro = true; + Log.w(TAG, "Low-memory device: shared RELROs used in all processes"); + } else { + mBrowserUsesSharedRelro = false; + } + break; + case BROWSER_SHARED_RELRO_CONFIG_ALWAYS: + Log.w(TAG, "Beware: shared RELROs used in all processes!"); + mBrowserUsesSharedRelro = true; + break; + default: + assert false : "Unreached"; + break; + } + } else { + if (DEBUG) { + Log.i(TAG, "Linker disabled"); + } + } + + if (!mRelroSharingSupported) { + // Sanity. + mBrowserUsesSharedRelro = false; + mWaitForSharedRelros = false; + } + + mInitialized = true; + } + + /** + * Call this method to determine if this chromium project must + * use this linker. If not, System.loadLibrary() should be used to load + * libraries instead. + */ + @Override + public boolean isUsed() { + // Only GYP targets that are APKs and have the 'use_chromium_linker' variable + // defined as 1 will use this linker. For all others (the default), the + // auto-generated NativeLibraries.sUseLinker variable will be false. + if (!NativeLibraries.sUseLinker) return false; + + synchronized (mLock) { + ensureInitializedLocked(); + // At the moment, there is also no point in using this linker if the + // system does not support RELRO sharing safely. + return mRelroSharingSupported; + } + } + + /** + * Call this method to determine if the linker will try to use shared RELROs + * for the browser process. + */ + @Override + public boolean isUsingBrowserSharedRelros() { + synchronized (mLock) { + ensureInitializedLocked(); + return mBrowserUsesSharedRelro; + } + } + + /** + * Call this method to determine if the chromium project must load + * the library directly from the zip file. + */ + @Override + public boolean isInZipFile() { + return NativeLibraries.sUseLibraryInZipFile; + } + + /** + * Call this method just before loading any native shared libraries in this process. + */ + @Override + public void prepareLibraryLoad() { + if (DEBUG) { + Log.i(TAG, "prepareLibraryLoad() called"); + } + synchronized (mLock) { + mPrepareLibraryLoadCalled = true; + + if (mInBrowserProcess) { + // Force generation of random base load address, as well + // as creation of shared RELRO sections in this process. + setupBaseLoadAddressLocked(); + } + } + } + + /** + * Call this method just after loading all native shared libraries in this process. + * Note that when in a service process, this will block until the RELRO bundle is + * received, i.e. when another thread calls useSharedRelros(). + */ + @Override + public void finishLibraryLoad() { + if (DEBUG) { + Log.i(TAG, "finishLibraryLoad() called"); + } + synchronized (mLock) { + if (DEBUG) { + Log.i(TAG, + String.format(Locale.US, + "mInBrowserProcess=%s mBrowserUsesSharedRelro=%s mWaitForSharedRelros=%s", + mInBrowserProcess ? "true" : "false", + mBrowserUsesSharedRelro ? "true" : "false", + mWaitForSharedRelros ? "true" : "false")); + } + + if (mLoadedLibraries == null) { + if (DEBUG) { + Log.i(TAG, "No libraries loaded"); + } + } else { + if (mInBrowserProcess) { + // Create new Bundle containing RELRO section information + // for all loaded libraries. Make it available to getSharedRelros(). + mSharedRelros = createBundleFromLibInfoMap(mLoadedLibraries); + if (DEBUG) { + Log.i(TAG, "Shared RELRO created"); + dumpBundle(mSharedRelros); + } + + if (mBrowserUsesSharedRelro) { + useSharedRelrosLocked(mSharedRelros); + } + } + + if (mWaitForSharedRelros) { + assert !mInBrowserProcess; + + // Wait until the shared relro bundle is received from useSharedRelros(). + while (mSharedRelros == null) { + try { + mLock.wait(); + } catch (InterruptedException ie) { + // no-op + } + } + useSharedRelrosLocked(mSharedRelros); + // Clear the Bundle to ensure its file descriptor references can't be reused. + mSharedRelros.clear(); + mSharedRelros = null; + } + } + + if (NativeLibraries.sEnableLinkerTests && mTestRunnerClassName != null) { + // The TestRunner implementation must be instantiated _after_ + // all libraries are loaded to ensure that its native methods + // are properly registered. + if (DEBUG) { + Log.i(TAG, "Instantiating " + mTestRunnerClassName); + } + TestRunner testRunner = null; + try { + testRunner = (TestRunner) Class.forName(mTestRunnerClassName).newInstance(); + } catch (Exception e) { + Log.e(TAG, "Could not extract test runner class name", e); + testRunner = null; + } + if (testRunner != null) { + if (!testRunner.runChecks(mMemoryDeviceConfig, mInBrowserProcess)) { + Log.wtf(TAG, "Linker runtime tests failed in this process!!"); + assert false; + } else { + Log.i(TAG, "All linker tests passed!"); + } + } + } + } + if (DEBUG) { + Log.i(TAG, "finishLibraryLoad() exiting"); + } + } + + /** + * Call this to send a Bundle containing the shared RELRO sections to be + * used in this process. If initServiceProcess() was previously called, + * finishLibraryLoad() will not exit until this method is called in another + * thread with a non-null value. + * @param bundle The Bundle instance containing a map of shared RELRO sections + * to use in this process. + */ + @Override + public void useSharedRelros(Bundle bundle) { + // Ensure the bundle uses the application's class loader, not the framework + // one which doesn't know anything about LibInfo. + // Also, hold a fresh copy of it so the caller can't recycle it. + Bundle clonedBundle = null; + if (bundle != null) { + bundle.setClassLoader(LibInfo.class.getClassLoader()); + clonedBundle = new Bundle(LibInfo.class.getClassLoader()); + Parcel parcel = Parcel.obtain(); + bundle.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + clonedBundle.readFromParcel(parcel); + parcel.recycle(); + } + if (DEBUG) { + Log.i(TAG, "useSharedRelros() called with " + bundle + ", cloned " + clonedBundle); + } + synchronized (mLock) { + // Note that in certain cases, this can be called before + // initServiceProcess() in service processes. + mSharedRelros = clonedBundle; + // Tell any listener blocked in finishLibraryLoad() about it. + mLock.notifyAll(); + } + } + + /** + * Call this to retrieve the shared RELRO sections created in this process, + * after loading all libraries. + * @return a new Bundle instance, or null if RELRO sharing is disabled on + * this system, or if initServiceProcess() was called previously. + */ + @Override + public Bundle getSharedRelros() { + if (DEBUG) { + Log.i(TAG, "getSharedRelros() called"); + } + synchronized (mLock) { + if (!mInBrowserProcess) { + if (DEBUG) { + Log.i(TAG, "... returning null Bundle"); + } + return null; + } + + // Return the Bundle created in finishLibraryLoad(). + if (DEBUG) { + Log.i(TAG, "... returning " + mSharedRelros); + } + return mSharedRelros; + } + } + + /** + * Call this method before loading any libraries to indicate that this + * process shall neither create or reuse shared RELRO sections. + */ + @Override + public void disableSharedRelros() { + if (DEBUG) { + Log.i(TAG, "disableSharedRelros() called"); + } + synchronized (mLock) { + mInBrowserProcess = false; + mWaitForSharedRelros = false; + mBrowserUsesSharedRelro = false; + } + } + + /** + * Call this method before loading any libraries to indicate that this + * process is ready to reuse shared RELRO sections from another one. + * Typically used when starting service processes. + * @param baseLoadAddress the base library load address to use. + */ + @Override + public void initServiceProcess(long baseLoadAddress) { + if (DEBUG) { + Log.i(TAG, + String.format(Locale.US, "initServiceProcess(0x%x) called", baseLoadAddress)); + } + synchronized (mLock) { + ensureInitializedLocked(); + mInBrowserProcess = false; + mBrowserUsesSharedRelro = false; + if (mRelroSharingSupported) { + mWaitForSharedRelros = true; + mBaseLoadAddress = baseLoadAddress; + mCurrentLoadAddress = baseLoadAddress; + } + } + } + + /** + * Retrieve the base load address of all shared RELRO sections. + * This also enforces the creation of shared RELRO sections in + * prepareLibraryLoad(), which can later be retrieved with getSharedRelros(). + * @return a common, random base load address, or 0 if RELRO sharing is + * disabled. + */ + @Override + public long getBaseLoadAddress() { + synchronized (mLock) { + ensureInitializedLocked(); + if (!mInBrowserProcess) { + Log.w(TAG, "Shared RELRO sections are disabled in this process!"); + return 0; + } + + setupBaseLoadAddressLocked(); + if (DEBUG) { + Log.i(TAG, String.format(Locale.US, "getBaseLoadAddress() returns 0x%x", + mBaseLoadAddress)); + } + return mBaseLoadAddress; + } + } + + // Used internally to lazily setup the common random base load address. + private void setupBaseLoadAddressLocked() { + assert Thread.holdsLock(mLock); + if (mBaseLoadAddress == 0) { + long address = computeRandomBaseLoadAddress(); + mBaseLoadAddress = address; + mCurrentLoadAddress = address; + if (address == 0) { + // If the computed address is 0, there are issues with finding enough + // free address space, so disable RELRO shared / fixed load addresses. + Log.w(TAG, "Disabling shared RELROs due address space pressure"); + mBrowserUsesSharedRelro = false; + mWaitForSharedRelros = false; + } + } + } + + /** + * Compute a random base load address at which to place loaded libraries. + * @return new base load address, or 0 if the system does not support + * RELRO sharing. + */ + private long computeRandomBaseLoadAddress() { + // nativeGetRandomBaseLoadAddress() returns an address at which it has previously + // successfully mapped an area of the given size, on the basis that we will be + // able, with high probability, to map our library into it. + // + // One issue with this is that we do not yet know the size of the library that + // we will load is. So here we pass a value that we expect will always be larger + // than that needed. If it is smaller the library mapping may still succeed. The + // other issue is that although highly unlikely, there is no guarantee that + // something else does not map into the area we are going to use between here and + // when we try to map into it. + // + // The above notes mean that all of this is probablistic. It is however okay to do + // because if, worst case and unlikely, we get unlucky in our choice of address, + // the back-out and retry without the shared RELRO in the ChildProcessService will + // keep things running. + final long maxExpectedBytes = 192 * 1024 * 1024; + final long address = nativeGetRandomBaseLoadAddress(maxExpectedBytes); + if (DEBUG) { + Log.i(TAG, String.format(Locale.US, "Random native base load address: 0x%x", address)); + } + return address; + } + + // Used for debugging only. + private void dumpBundle(Bundle bundle) { + if (DEBUG) { + Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle); + } + } + + /** + * Use the shared RELRO section from a Bundle received form another process. + * Call this after calling setBaseLoadAddress() then loading all libraries + * with loadLibrary(). + * @param bundle Bundle instance generated with createSharedRelroBundle() in + * another process. + */ + private void useSharedRelrosLocked(Bundle bundle) { + assert Thread.holdsLock(mLock); + + if (DEBUG) { + Log.i(TAG, "Linker.useSharedRelrosLocked() called"); + } + + if (bundle == null) { + if (DEBUG) { + Log.i(TAG, "null bundle!"); + } + return; + } + + if (!mRelroSharingSupported) { + if (DEBUG) { + Log.i(TAG, "System does not support RELRO sharing"); + } + return; + } + + if (mLoadedLibraries == null) { + if (DEBUG) { + Log.i(TAG, "No libraries loaded!"); + } + return; + } + + if (DEBUG) { + dumpBundle(bundle); + } + HashMap<String, LibInfo> relroMap = createLibInfoMapFromBundle(bundle); + + // Apply the RELRO section to all libraries that were already loaded. + for (Map.Entry<String, LibInfo> entry : relroMap.entrySet()) { + String libName = entry.getKey(); + LibInfo libInfo = entry.getValue(); + if (!nativeUseSharedRelro(libName, libInfo)) { + Log.w(TAG, "Could not use shared RELRO section for " + libName); + } else { + if (DEBUG) { + Log.i(TAG, "Using shared RELRO section for " + libName); + } + } + } + + // In service processes, close all file descriptors from the map now. + if (!mInBrowserProcess) closeLibInfoMap(relroMap); + + if (DEBUG) { + Log.i(TAG, "Linker.useSharedRelrosLocked() exiting"); + } + } + + /** + * Load a native shared library with the Chromium linker. If the zip file + * is not null, the shared library must be uncompressed and page aligned + * inside the zipfile. Note the crazy linker treats libraries and files as + * equivalent, so you can only open one library in a given zip file. The + * library must not be the Chromium linker library. + * + * @param zipFilePath The path of the zip file containing the library (or null). + * @param libFilePath The path of the library (possibly in the zip file). + */ + @Override + public void loadLibrary(@Nullable String zipFilePath, String libFilePath) { + if (DEBUG) { + Log.i(TAG, "loadLibrary: " + zipFilePath + ", " + libFilePath); + } + + synchronized (mLock) { + ensureInitializedLocked(); + + // Security: Ensure prepareLibraryLoad() was called before. + // In theory, this can be done lazily here, but it's more consistent + // to use a pair of functions (i.e. prepareLibraryLoad() + finishLibraryLoad()) + // that wrap all calls to loadLibrary() in the library loader. + assert mPrepareLibraryLoadCalled; + + if (mLoadedLibraries == null) { + mLoadedLibraries = new HashMap<String, LibInfo>(); + } + + if (mLoadedLibraries.containsKey(libFilePath)) { + if (DEBUG) { + Log.i(TAG, "Not loading " + libFilePath + " twice"); + } + return; + } + + LibInfo libInfo = new LibInfo(); + long loadAddress = 0; + if ((mInBrowserProcess && mBrowserUsesSharedRelro) || mWaitForSharedRelros) { + // Load the library at a fixed address. + loadAddress = mCurrentLoadAddress; + } + + String sharedRelRoName = libFilePath; + if (zipFilePath != null) { + if (!nativeLoadLibraryInZipFile(zipFilePath, libFilePath, loadAddress, libInfo)) { + String errorMessage = + "Unable to load library: " + libFilePath + ", in: " + zipFilePath; + Log.e(TAG, errorMessage); + throw new UnsatisfiedLinkError(errorMessage); + } + sharedRelRoName = zipFilePath; + } else { + if (!nativeLoadLibrary(libFilePath, loadAddress, libInfo)) { + String errorMessage = "Unable to load library: " + libFilePath; + Log.e(TAG, errorMessage); + throw new UnsatisfiedLinkError(errorMessage); + } + } + + // Print the load address to the logcat when testing the linker. The format + // of the string is expected by the Python test_runner script as one of: + // BROWSER_LIBRARY_ADDRESS: <library-name> <address> + // RENDERER_LIBRARY_ADDRESS: <library-name> <address> + // Where <library-name> is the library name, and <address> is the hexadecimal load + // address. + if (NativeLibraries.sEnableLinkerTests) { + Log.i(TAG, String.format(Locale.US, "%s_LIBRARY_ADDRESS: %s %x", + mInBrowserProcess ? "BROWSER" : "RENDERER", libFilePath, + libInfo.mLoadAddress)); + } + + if (mInBrowserProcess) { + // Create a new shared RELRO section at the 'current' fixed load address. + if (!nativeCreateSharedRelro(sharedRelRoName, mCurrentLoadAddress, libInfo)) { + Log.w(TAG, + String.format(Locale.US, "Could not create shared RELRO for %s at %x", + libFilePath, mCurrentLoadAddress)); + } else { + if (DEBUG) { + Log.i(TAG, + String.format(Locale.US, "Created shared RELRO for %s at %x: %s", + sharedRelRoName, mCurrentLoadAddress, libInfo.toString())); + } + } + } + + if (mCurrentLoadAddress != 0) { + // Compute the next current load address. If mBaseLoadAddress + // is not 0, this is an explicit library load address. Otherwise, + // this is an explicit load address for relocated RELRO sections + // only. + mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize; + } + + mLoadedLibraries.put(sharedRelRoName, libInfo); + if (DEBUG) { + Log.i(TAG, "Library details " + libInfo.toString()); + } + } + } + + /** + * Determine whether a library is the linker library. Also deal with the + * component build that adds a .cr suffix to the name. + */ + @Override + public boolean isChromiumLinkerLibrary(String library) { + return library.equals(LINKER_JNI_LIBRARY) || library.equals(LINKER_JNI_LIBRARY + ".cr"); + } + + /** + * Move activity from the native thread to the main UI thread. + * Called from native code on its own thread. Posts a callback from + * the UI thread back to native code. + * + * @param opaque Opaque argument. + */ + @CalledByNative + public static void postCallbackOnMainThread(final long opaque) { + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + nativeRunCallbackOnUiThread(opaque); + } + }); + } + + /** + * Native method to run callbacks on the main UI thread. + * Supplied by the crazy linker and called by postCallbackOnMainThread. + * @param opaque Opaque crazy linker arguments. + */ + private static native void nativeRunCallbackOnUiThread(long opaque); + + /** + * Native method used to load a library. + * @param library Platform specific library name (e.g. libfoo.so) + * @param loadAddress Explicit load address, or 0 for randomized one. + * @param libInfo If not null, the mLoadAddress and mLoadSize fields + * of this LibInfo instance will set on success. + * @return true for success, false otherwise. + */ + private static native boolean nativeLoadLibrary( + String library, long loadAddress, LibInfo libInfo); + + /** + * Native method used to load a library which is inside a zipfile. + * @param zipfileName Filename of the zip file containing the library. + * @param library Platform specific library name (e.g. libfoo.so) + * @param loadAddress Explicit load address, or 0 for randomized one. + * @param libInfo If not null, the mLoadAddress and mLoadSize fields + * of this LibInfo instance will set on success. + * @return true for success, false otherwise. + */ + private static native boolean nativeLoadLibraryInZipFile( + String zipfileName, String libraryName, long loadAddress, LibInfo libInfo); + + /** + * Native method used to create a shared RELRO section. + * If the library was already loaded at the same address using + * nativeLoadLibrary(), this creates the RELRO for it. Otherwise, + * this loads a new temporary library at the specified address, + * creates and extracts the RELRO section from it, then unloads it. + * @param library Library name. + * @param loadAddress load address, which can be different from the one + * used to load the library in the current process! + * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize + * and mRelroFd will be set. + * @return true on success, false otherwise. + */ + private static native boolean nativeCreateSharedRelro( + String library, long loadAddress, LibInfo libInfo); + + /** + * Native method used to use a shared RELRO section. + * @param library Library name. + * @param libInfo A LibInfo instance containing valid RELRO information + * @return true on success. + */ + private static native boolean nativeUseSharedRelro(String library, LibInfo libInfo); + + /** + * Checks that the system supports shared RELROs. Old Android kernels + * have a bug in the way they check Ashmem region protection flags, which + * makes using shared RELROs unsafe. This method performs a simple runtime + * check for this misfeature, even though nativeEnableSharedRelro() will + * always fail if this returns false. + */ + private static native boolean nativeCanUseSharedRelro(); + + /** + * Return a random address that should be free to be mapped with the given size. + * Maps an area of size bytes, and if successful then unmaps it and returns + * the address of the area allocated by the system (with ASLR). The idea is + * that this area should remain free of other mappings until we map our library + * into it. + * @param sizeBytes Size of area in bytes to search for. + * @return address to pass to future mmap, or 0 on error. + */ + private static native long nativeGetRandomBaseLoadAddress(long sizeBytes); +}
diff --git a/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/android/java/src/org/chromium/base/library_loader/LibraryLoader.java index a789eff..5dfc2ba 100644 --- a/android/java/src/org/chromium/base/library_loader/LibraryLoader.java +++ b/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -11,16 +11,17 @@ import android.os.AsyncTask; import android.os.Build; import android.os.SystemClock; -import android.util.Log; import org.chromium.base.CalledByNative; import org.chromium.base.CommandLine; import org.chromium.base.JNINamespace; +import org.chromium.base.Log; import org.chromium.base.PackageUtils; import org.chromium.base.TraceEvent; -import org.chromium.base.VisibleForTesting; import org.chromium.base.metrics.RecordHistogram; +import java.util.concurrent.atomic.AtomicBoolean; + import javax.annotation.Nullable; /** @@ -39,7 +40,7 @@ */ @JNINamespace("base::android") public class LibraryLoader { - private static final String TAG = "LibraryLoader"; + private static final String TAG = "cr.library_loader"; // Set to true to enable debug logs. private static final boolean DEBUG = false; @@ -73,15 +74,19 @@ // APK file directly. private boolean mLibraryWasLoadedFromApk; - // One-way switch becomes false if the Chromium library should be loaded - // directly from the APK file but it was compressed or not aligned. - private boolean mLibraryIsMappableInApk = true; - // The type of process the shared library is loaded in. // This member can be accessed from multiple threads simultaneously, so it have to be // final (like now) or be protected in some way (volatile of synchronized). private final int mLibraryProcessType; + // One-way switch that becomes true once + // {@link asyncPrefetchLibrariesToMemory} has been called. + private final AtomicBoolean mPrefetchLibraryHasBeenCalled; + + // The number of milliseconds it took to load all the native libraries, which + // will be reported via UMA. Set once when the libraries are done loading. + private long mLibraryLoadTimeMs; + /** * @param libraryProcessType the process the shared library is loaded in. refer to * LibraryProcessType for possible values. @@ -101,38 +106,21 @@ private LibraryLoader(int libraryProcessType) { mLibraryProcessType = libraryProcessType; - } - - /** - * The same as ensureInitialized(null, false), should only be called - * by non-browser processes. - * - * @throws ProcessInitException - */ - @VisibleForTesting - public void ensureInitialized() throws ProcessInitException { - ensureInitialized(null, false); + mPrefetchLibraryHasBeenCalled = new AtomicBoolean(); } /** * This method blocks until the library is fully loaded and initialized. * - * @param context The context in which the method is called, the caller - * may pass in a null context if it doesn't know in which context it - * is running. - * - * @param shouldDeleteFallbackLibraries The flag tells whether the method - * should delete the fallback libraries or not. + * @param context The context in which the method is called. */ - public void ensureInitialized( - Context context, boolean shouldDeleteFallbackLibraries) - throws ProcessInitException { + public void ensureInitialized(Context context) throws ProcessInitException { synchronized (sLock) { if (mInitialized) { // Already initialized, nothing to do. return; } - loadAlreadyLocked(context, shouldDeleteFallbackLibraries); + loadAlreadyLocked(context); initializeAlreadyLocked(); } } @@ -145,32 +133,19 @@ } /** - * The same as loadNow(null, false), should only be called by - * non-browser process. - * - * @throws ProcessInitException - */ - public void loadNow() throws ProcessInitException { - loadNow(null, false); - } - - /** * Loads the library and blocks until the load completes. The caller is responsible * for subsequently calling ensureInitialized(). * May be called on any thread, but should only be called once. Note the thread * this is called on will be the thread that runs the native code's static initializers. * See the comment in doInBackground() for more considerations on this. * - * @param context The context the code is running, or null if it doesn't have one. - * @param shouldDeleteFallbackLibraries The flag tells whether the method - * should delete the old fallback libraries or not. + * @param context The context the code is running. * * @throws ProcessInitException if the native library failed to load. */ - public void loadNow(Context context, boolean shouldDeleteFallbackLibraries) - throws ProcessInitException { + public void loadNow(Context context) throws ProcessInitException { synchronized (sLock) { - loadAlreadyLocked(context, shouldDeleteFallbackLibraries); + loadAlreadyLocked(context); } } @@ -193,10 +168,9 @@ * * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is * detrimental to the startup time. - * - * @param context the application context. */ - public void asyncPrefetchLibrariesToMemory(final Context context) { + public void asyncPrefetchLibrariesToMemory() { + if (!mPrefetchLibraryHasBeenCalled.compareAndSet(false, true)) return; new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { @@ -213,34 +187,26 @@ } // Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code - private void loadAlreadyLocked( - Context context, boolean shouldDeleteFallbackLibraries) - throws ProcessInitException { + private void loadAlreadyLocked(Context context) throws ProcessInitException { try { if (!mLoaded) { assert !mInitialized; long startTime = SystemClock.uptimeMillis(); - boolean useChromiumLinker = Linker.isUsed(); - boolean fallbackWasUsed = false; + Linker linker = Linker.getInstance(); + boolean useChromiumLinker = linker.isUsed(); if (useChromiumLinker) { // Determine the APK file path. - String apkFilePath = null; - if (context != null) { - apkFilePath = getLibraryApkPath(context); - } else { - Log.w(TAG, "could not check load from APK support due to null context"); - } - + String apkFilePath = getLibraryApkPath(context); // Load libraries using the Chromium linker. - Linker.prepareLibraryLoad(); + linker.prepareLibraryLoad(); for (String library : NativeLibraries.LIBRARIES) { // Don't self-load the linker. This is because the build system is // not clever enough to understand that all the libraries packaged // in the final .apk don't need to be explicitly loaded. - if (Linker.isChromiumLinkerLibrary(library)) { + if (linker.isChromiumLinkerLibrary(library)) { if (DEBUG) Log.i(TAG, "ignoring self-linker load"); continue; } @@ -248,26 +214,11 @@ // Determine where the library should be loaded from. String zipFilePath = null; String libFilePath = System.mapLibraryName(library); - if (apkFilePath != null && Linker.isInZipFile()) { - // The library is in the APK file. - if (!Linker.checkLibraryIsMappableInApk(apkFilePath, libFilePath)) { - mLibraryIsMappableInApk = false; - } - if (mLibraryIsMappableInApk) { - // Load directly from the APK. - zipFilePath = apkFilePath; - Log.i(TAG, "Loading " + library + " directly from within " - + apkFilePath); - } else { - // Unpack library fallback. - Log.i(TAG, "Loading " + library - + " using unpack library fallback from within " - + apkFilePath); - libFilePath = LibraryLoaderHelper.buildFallbackLibrary( - context, library); - fallbackWasUsed = true; - Log.i(TAG, "Built fallback library " + libFilePath); - } + if (linker.isInZipFile()) { + // Load directly from the APK. + zipFilePath = apkFilePath; + Log.i(TAG, + "Loading " + library + " directly from within " + apkFilePath); } else { // The library is in its own file. Log.i(TAG, "Loading " + library); @@ -275,7 +226,7 @@ // Load the library. boolean isLoaded = false; - if (Linker.isUsingBrowserSharedRelros()) { + if (linker.isUsingBrowserSharedRelros()) { mIsUsingBrowserSharedRelros = true; try { loadLibrary(zipFilePath, libFilePath); @@ -283,7 +234,7 @@ } catch (UnsatisfiedLinkError e) { Log.w(TAG, "Failed to load native library with shared RELRO, " + "retrying without"); - Linker.disableSharedRelros(); + linker.disableSharedRelros(); mLoadAtFixedAddressFailed = true; } } @@ -292,7 +243,7 @@ } } - Linker.finishLibraryLoad(); + linker.finishLibraryLoad(); } else { // Load libraries using the system linker. for (String library : NativeLibraries.LIBRARIES) { @@ -300,17 +251,10 @@ } } - if (!fallbackWasUsed && context != null - && shouldDeleteFallbackLibraries) { - LibraryLoaderHelper.deleteLibrariesAsynchronously( - context, LibraryLoaderHelper.LOAD_FROM_APK_FALLBACK_DIR); - } - long stopTime = SystemClock.uptimeMillis(); + mLibraryLoadTimeMs = stopTime - startTime; Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)", - stopTime - startTime, - startTime % 10000, - stopTime % 10000)); + mLibraryLoadTimeMs, startTime % 10000, stopTime % 10000)); mLoaded = true; } @@ -318,11 +262,9 @@ throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e); } // Check that the version of the library we have loaded matches the version we expect - Log.i(TAG, String.format( - "Expected native library version number \"%s\"," - + "actual native library version number \"%s\"", - NativeLibraries.sVersionNumber, - nativeGetVersionNumber())); + Log.i(TAG, String.format("Expected native library version number \"%s\", " + + "actual native library version number \"%s\"", + NativeLibraries.sVersionNumber, nativeGetVersionNumber())); if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) { throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION); } @@ -356,7 +298,7 @@ // Load a native shared library with the Chromium linker. If the zip file // path is not null, the library is loaded directly from the zip file. private void loadLibrary(@Nullable String zipFilePath, String libFilePath) { - Linker.loadLibrary(zipFilePath, libFilePath); + Linker.getInstance().loadLibrary(zipFilePath, libFilePath); if (zipFilePath != null) { mLibraryWasLoadedFromApk = true; } @@ -426,26 +368,22 @@ // Record Chromium linker histogram state for the main browser process. Called from // onNativeInitializationComplete(). private void recordBrowserProcessHistogram(Context context) { - if (Linker.isUsed()) { + if (Linker.getInstance().isUsed()) { nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros, - mLoadAtFixedAddressFailed, - getLibraryLoadFromApkStatus(context)); + mLoadAtFixedAddressFailed, getLibraryLoadFromApkStatus(context), + mLibraryLoadTimeMs); } } // Returns the device's status for loading a library directly from the APK file. // This method can only be called when the Chromium linker is used. private int getLibraryLoadFromApkStatus(Context context) { - assert Linker.isUsed(); + assert Linker.getInstance().isUsed(); if (mLibraryWasLoadedFromApk) { return LibraryLoadFromApkStatusCodes.SUCCESSFUL; } - if (!mLibraryIsMappableInApk) { - return LibraryLoadFromApkStatusCodes.USED_UNPACK_LIBRARY_FALLBACK; - } - // There were no libraries to be loaded directly from the APK file. return LibraryLoadFromApkStatusCodes.UNKNOWN; } @@ -456,9 +394,9 @@ // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly. public void registerRendererProcessHistogram(boolean requestedSharedRelro, boolean loadAtFixedAddressFailed) { - if (Linker.isUsed()) { - nativeRegisterChromiumAndroidLinkerRendererHistogram(requestedSharedRelro, - loadAtFixedAddressFailed); + if (Linker.getInstance().isUsed()) { + nativeRegisterChromiumAndroidLinkerRendererHistogram( + requestedSharedRelro, loadAtFixedAddressFailed, mLibraryLoadTimeMs); } } @@ -485,18 +423,18 @@ // Method called to record statistics about the Chromium linker operation for the main // browser process. Indicates whether the linker attempted relro sharing for the browser, // and if it did, whether the library failed to load at a fixed address. Also records - // support for loading a library directly from the APK file. + // support for loading a library directly from the APK file, and the number of milliseconds + // it took to load the libraries. private native void nativeRecordChromiumAndroidLinkerBrowserHistogram( - boolean isUsingBrowserSharedRelros, - boolean loadAtFixedAddressFailed, - int libraryLoadFromApkStatus); + boolean isUsingBrowserSharedRelros, boolean loadAtFixedAddressFailed, + int libraryLoadFromApkStatus, long libraryLoadTime); // Method called to register (for later recording) statistics about the Chromium linker // operation for a renderer process. Indicates whether the linker attempted relro sharing, - // and if it did, whether the library failed to load at a fixed address. + // and if it did, whether the library failed to load at a fixed address. Also records the + // number of milliseconds it took to load the libraries. private native void nativeRegisterChromiumAndroidLinkerRendererHistogram( - boolean requestedSharedRelro, - boolean loadAtFixedAddressFailed); + boolean requestedSharedRelro, boolean loadAtFixedAddressFailed, long libraryLoadTime); // Get the version of the native library. This is needed so that we can check we // have the right version before initializing the (rest of the) JNI.
diff --git a/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java b/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java deleted file mode 100644 index 7dd1a29..0000000 --- a/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java +++ /dev/null
@@ -1,338 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - - -package org.chromium.base.library_loader; - -import android.content.Context; -import android.util.Log; - -import java.io.BufferedOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; - -/** - * Class representing an exception which occured during the unpacking process. - */ -class UnpackingException extends Exception { - public UnpackingException(String message, Throwable cause) { - super(message, cause); - } - - public UnpackingException(String message) { - super(message); - } -} - -/** - * The class provides helper functions to extract native libraries from APK, - * and load libraries from there. - */ -class LibraryLoaderHelper { - private static final String TAG = "LibraryLoaderHelper"; - - // Fallback directories. - static final String LOAD_FROM_APK_FALLBACK_DIR = "fallback"; - - private static final int BUFFER_SIZE = 16384; - - /** - * Returns the directory for holding extracted native libraries. - * It may create the directory if it doesn't exist. - * - * @param context The context the code is running. - * @param dirName The name of the directory containing the libraries. - * @return The directory file object. - */ - static File getLibDir(Context context, String dirName) { - return context.getDir(dirName, Context.MODE_PRIVATE); - } - - /** - * Delete libraries and their directory synchronously. - */ - private static void deleteLibrariesSynchronously(Context context, String dirName) { - File libDir = getLibDir(context, dirName); - deleteObsoleteLibraries(libDir, Collections.<File>emptyList()); - } - - /** - * Delete libraries and their directory asynchronously. - * The actual deletion is done in a background thread. - */ - static void deleteLibrariesAsynchronously( - final Context context, final String dirName) { - // Child process should not reach here. - new Thread() { - @Override - public void run() { - deleteLibrariesSynchronously(context, dirName); - } - }.start(); - } - - /** - * Copy a library from a zip file to the application's private directory. - * This is used as a fallback when we are unable to load the library - * directly from the APK file (crbug.com/390618). - * - * @param context The context the code is running in. - * @param library Library name. - * @return name of the fallback copy of the library. - */ - static String buildFallbackLibrary(Context context, String library) { - try { - String libName = System.mapLibraryName(library); - File fallbackLibDir = getLibDir(context, LOAD_FROM_APK_FALLBACK_DIR); - File fallbackLibFile = new File(fallbackLibDir, libName); - String pathInZipFile = Linker.getLibraryFilePathInZipFile(libName); - Map<String, File> dstFiles = Collections.singletonMap(pathInZipFile, fallbackLibFile); - - deleteObsoleteLibraries(fallbackLibDir, dstFiles.values()); - unpackLibraries(context, dstFiles); - - return fallbackLibFile.getAbsolutePath(); - } catch (Exception e) { - String errorMessage = "Unable to load fallback for library " + library - + " (" + (e.getMessage() == null ? e.toString() : e.getMessage()) + ")"; - Log.e(TAG, errorMessage, e); - throw new UnsatisfiedLinkError(errorMessage); - } - } - - // Delete obsolete libraries from a library folder. - private static void deleteObsoleteLibraries(File libDir, Collection<File> keptFiles) { - try { - // Build a list of libraries that should NOT be deleted. - Set<String> keptFileNames = new HashSet<String>(); - for (File k : keptFiles) { - keptFileNames.add(k.getName()); - } - - // Delete the obsolete libraries. - Log.i(TAG, "Deleting obsolete libraries in " + libDir.getPath()); - File[] files = libDir.listFiles(); - if (files != null) { - for (File f : files) { - if (!keptFileNames.contains(f.getName())) { - delete(f); - } - } - } else { - Log.e(TAG, "Failed to list files in " + libDir.getPath()); - } - - // Delete the folder if no libraries were kept. - if (keptFileNames.isEmpty()) { - delete(libDir); - } - } catch (Exception e) { - Log.e(TAG, "Failed to remove obsolete libraries from " + libDir.getPath()); - } - } - - // Unpack libraries from a zip file to the file system. - private static void unpackLibraries(Context context, - Map<String, File> dstFiles) throws UnpackingException { - String zipFilePath = context.getApplicationInfo().sourceDir; - Log.i(TAG, "Opening zip file " + zipFilePath); - File zipFile = new File(zipFilePath); - ZipFile zipArchive = openZipFile(zipFile); - - try { - for (Entry<String, File> d : dstFiles.entrySet()) { - String pathInZipFile = d.getKey(); - File dstFile = d.getValue(); - Log.i(TAG, "Unpacking " + pathInZipFile - + " to " + dstFile.getAbsolutePath()); - ZipEntry packedLib = zipArchive.getEntry(pathInZipFile); - - if (needToUnpackLibrary(zipFile, packedLib, dstFile)) { - unpackLibraryFromZipFile(zipArchive, packedLib, dstFile); - setLibraryFilePermissions(dstFile); - } - } - } finally { - closeZipFile(zipArchive); - } - } - - // Open a zip file. - private static ZipFile openZipFile(File zipFile) throws UnpackingException { - try { - return new ZipFile(zipFile); - } catch (ZipException e) { - throw new UnpackingException("Failed to open zip file " + zipFile.getPath()); - } catch (IOException e) { - throw new UnpackingException("Failed to open zip file " + zipFile.getPath()); - } - } - - // Determine whether it is necessary to unpack a library from a zip file. - private static boolean needToUnpackLibrary( - File zipFile, ZipEntry packedLib, File dstFile) { - // Check if the fallback library already exists. - if (!dstFile.exists()) { - Log.i(TAG, "File " + dstFile.getPath() + " does not exist yet"); - return true; - } - - // Check last modification dates. - long zipTime = zipFile.lastModified(); - long fallbackLibTime = dstFile.lastModified(); - if (zipTime > fallbackLibTime) { - Log.i(TAG, "Not using existing fallback file because " - + "the APK file " + zipFile.getPath() - + " (timestamp=" + zipTime + ") is newer than " - + "the fallback library " + dstFile.getPath() - + "(timestamp=" + fallbackLibTime + ")"); - return true; - } - - // Check file sizes. - long packedLibSize = packedLib.getSize(); - long fallbackLibSize = dstFile.length(); - if (fallbackLibSize != packedLibSize) { - Log.i(TAG, "Not using existing fallback file because " - + "the library in the APK " + zipFile.getPath() - + " (" + packedLibSize + "B) has a different size than " - + "the fallback library " + dstFile.getPath() - + "(" + fallbackLibSize + "B)"); - return true; - } - - Log.i(TAG, "Reusing existing file " + dstFile.getPath()); - return false; - } - - // Unpack a library from a zip file to the filesystem. - private static void unpackLibraryFromZipFile(ZipFile zipArchive, ZipEntry packedLib, - File dstFile) throws UnpackingException { - // Open input stream for the library file inside the zip file. - InputStream in; - try { - in = zipArchive.getInputStream(packedLib); - } catch (IOException e) { - throw new UnpackingException( - "IO exception when locating library in the zip file", e); - } - - // Ensure that the input stream is closed at the end. - try { - // Delete existing file if it exists. - if (dstFile.exists()) { - Log.i(TAG, "Deleting existing unpacked library file " + dstFile.getPath()); - if (!dstFile.delete()) { - throw new UnpackingException( - "Failed to delete existing unpacked library file " + dstFile.getPath()); - } - } - - // Ensure that the library folder exists. Since this is added - // for increased robustness, we log errors and carry on. - try { - dstFile.getParentFile().mkdirs(); - } catch (Exception e) { - Log.e(TAG, "Failed to make library folder", e); - } - - // Create the destination file. - try { - if (!dstFile.createNewFile()) { - throw new UnpackingException("existing unpacked library file was not deleted"); - } - } catch (IOException e) { - throw new UnpackingException("failed to create unpacked library file", e); - } - - // Open the output stream for the destination file. - OutputStream out; - try { - out = new BufferedOutputStream(new FileOutputStream(dstFile)); - } catch (FileNotFoundException e) { - throw new UnpackingException( - "failed to open output stream for unpacked library file", e); - } - - // Ensure that the output stream is closed at the end. - try { - // Copy the library from the zip file to the destination file. - Log.i(TAG, "Copying " + packedLib.getName() + " from " + zipArchive.getName() - + " to " + dstFile.getPath()); - byte[] buffer = new byte[BUFFER_SIZE]; - int len; - while ((len = in.read(buffer)) != -1) { - out.write(buffer, 0, len); - } - } catch (IOException e) { - throw new UnpackingException( - "failed to copy the library from the zip file", e); - } finally { - close(out, "output stream"); - } - } finally { - close(in, "input stream"); - } - } - - // Set up library file permissions. - private static void setLibraryFilePermissions(File libFile) { - // Change permission to rwxr-xr-x - Log.i(TAG, "Setting file permissions for " + libFile.getPath()); - if (!libFile.setReadable(/* readable */ true, /* ownerOnly */ false)) { - Log.e(TAG, "failed to chmod a+r the temporary file"); - } - if (!libFile.setExecutable(/* executable */ true, /* ownerOnly */ false)) { - Log.e(TAG, "failed to chmod a+x the temporary file"); - } - if (!libFile.setWritable(/* writable */ true)) { - Log.e(TAG, "failed to chmod +w the temporary file"); - } - } - - // Close a closable and log a warning if it fails. - private static void close(Closeable closeable, String name) { - try { - closeable.close(); - } catch (IOException e) { - // Warn and ignore. - Log.w(TAG, "IO exception when closing " + name, e); - } - } - - // Close a zip file and log a warning if it fails. - // This needs to be a separate method because ZipFile is not Closeable in - // Java 6 (used on some older devices). - private static void closeZipFile(ZipFile file) { - try { - file.close(); - } catch (IOException e) { - // Warn and ignore. - Log.w(TAG, "IO exception when closing zip file", e); - } - } - - // Delete a file and log it. - private static void delete(File file) { - if (file.delete()) { - Log.i(TAG, "Deleted " + file.getPath()); - } else { - Log.w(TAG, "Failed to delete " + file.getPath()); - } - } -}
diff --git a/android/java/src/org/chromium/base/library_loader/Linker.java b/android/java/src/org/chromium/base/library_loader/Linker.java index 9a64a3f..c769339 100644 --- a/android/java/src/org/chromium/base/library_loader/Linker.java +++ b/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -1,4 +1,4 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -8,14 +8,10 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -import android.util.Log; -import org.chromium.base.CalledByNative; -import org.chromium.base.SysUtils; -import org.chromium.base.ThreadUtils; +import org.chromium.base.Log; import org.chromium.base.annotations.AccessedByNative; -import java.io.FileNotFoundException; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -153,13 +149,19 @@ * * This method also ensures the process uses the shared RELROs. */ -public class Linker { - - // Log tag for this class. This must match the name of the linker's native library. - private static final String TAG = "chromium_android_linker"; +public abstract class Linker { + // Log tag for this class. + private static final String TAG = "cr.library_loader"; // Set to true to enable debug logs. - private static final boolean DEBUG = false; + protected static final boolean DEBUG = false; + + // Used to pass the shared RELRO Bundle through Binder. + public static final String EXTRA_LINKER_SHARED_RELROS = + "org.chromium.base.android.linker.shared_relros"; + + // Guards all access to the linker. + protected final Object mLock = new Object(); // Constants used to control the behaviour of the browser process with // regards to the shared RELRO section. @@ -188,96 +190,24 @@ // Indicates if this is a low-memory device or not. The default is to // determine this by probing the system at runtime, but this can be forced // for testing by calling setMemoryDeviceConfig(). - private static int sMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT; + protected int mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT; - // Becomes true after linker initialization. - private static boolean sInitialized = false; + // Singleton. + private static Linker sSingleton = null; + private static Object sSingletonLock = new Object(); - // Set to true to indicate that the system supports safe sharing of RELRO sections. - private static boolean sRelroSharingSupported = false; + // Protected singleton constructor. + protected Linker() {} - // Set to true if this runs in the browser process. Disabled by initServiceProcess(). - // TODO(petrcermak): This flag can be incorrectly set to false (even though this might run in - // the browser process) on low-memory devices. - private static boolean sInBrowserProcess = true; - - // Becomes true to indicate this process needs to wait for a shared RELRO in - // finishLibraryLoad(). - private static boolean sWaitForSharedRelros = false; - - // Becomes true when initialization determines that the browser process can use the - // shared RELRO. - private static boolean sBrowserUsesSharedRelro = false; - - // The map of all RELRO sections either created or used in this process. - private static Bundle sSharedRelros = null; - - // Current common random base load address. - private static long sBaseLoadAddress = 0; - - // Current fixed-location load address for the next library called by loadLibrary(). - private static long sCurrentLoadAddress = 0; - - // Becomes true once prepareLibraryLoad() has been called. - private static boolean sPrepareLibraryLoadCalled = false; - - // Used internally to initialize the linker's static data. Assume lock is held. - private static void ensureInitializedLocked() { - assert Thread.holdsLock(Linker.class); - - if (!sInitialized) { - sRelroSharingSupported = false; - if (NativeLibraries.sUseLinker) { - if (DEBUG) Log.i(TAG, "Loading lib" + TAG + ".so"); - try { - System.loadLibrary(TAG); - } catch (UnsatisfiedLinkError e) { - // In a component build, the ".cr" suffix is added to each library name. - Log.w(TAG, "Couldn't load lib" + TAG + ".so, trying lib" + TAG + ".cr.so"); - System.loadLibrary(TAG + ".cr"); - } - sRelroSharingSupported = nativeCanUseSharedRelro(); - if (!sRelroSharingSupported) { - Log.w(TAG, "This system cannot safely share RELRO sections"); - } else { - if (DEBUG) Log.i(TAG, "This system supports safe shared RELRO sections"); - } - - if (sMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT) { - sMemoryDeviceConfig = SysUtils.isLowEndDevice() - ? MEMORY_DEVICE_CONFIG_LOW : MEMORY_DEVICE_CONFIG_NORMAL; - } - - switch (BROWSER_SHARED_RELRO_CONFIG) { - case BROWSER_SHARED_RELRO_CONFIG_NEVER: - sBrowserUsesSharedRelro = false; - break; - case BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY: - sBrowserUsesSharedRelro = - (sMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW); - if (sBrowserUsesSharedRelro) { - Log.w(TAG, "Low-memory device: shared RELROs used in all processes"); - } - break; - case BROWSER_SHARED_RELRO_CONFIG_ALWAYS: - Log.w(TAG, "Beware: shared RELROs used in all processes!"); - sBrowserUsesSharedRelro = true; - break; - default: - assert false : "Unreached"; - break; - } - } else { - if (DEBUG) Log.i(TAG, "Linker disabled"); + // Get singleton instance. + public static final Linker getInstance() { + synchronized (sSingletonLock) { + if (sSingleton == null) { + // TODO(simonb): Extend later to return either a LegacyLinker + // or a ModernLinker instance. + sSingleton = new LegacyLinker(); } - - if (!sRelroSharingSupported) { - // Sanity. - sBrowserUsesSharedRelro = false; - sWaitForSharedRelros = false; - } - - sInitialized = true; + return sSingleton; } } @@ -297,7 +227,7 @@ } // The name of a class that implements TestRunner. - static String sTestRunnerClassName = null; + String mTestRunnerClassName = null; /** * Set the TestRunner by its class name. It will be instantiated at @@ -305,17 +235,18 @@ * @param testRunnerClassName null or a String for the class name of the * TestRunner to use. */ - public static void setTestRunnerClassName(String testRunnerClassName) { - if (DEBUG) Log.i(TAG, "setTestRunnerByClassName(" + testRunnerClassName + ") called"); - + public void setTestRunnerClassName(String testRunnerClassName) { + if (DEBUG) { + Log.i(TAG, "setTestRunnerByClassName(" + testRunnerClassName + ") called"); + } if (!NativeLibraries.sEnableLinkerTests) { - // Ignore this in production code to prevent malvolent runtime injection. + // Ignore this in production code to prevent malevolent runtime injection. return; } - synchronized (Linker.class) { - assert sTestRunnerClassName == null; - sTestRunnerClassName = testRunnerClassName; + synchronized (mLock) { + assert mTestRunnerClassName == null; + mTestRunnerClassName = testRunnerClassName; } } @@ -326,9 +257,9 @@ * @return null or a String holding the name of the class implementing * the TestRunner set by calling setTestRunnerClassName() previously. */ - public static String getTestRunnerClassName() { - synchronized (Linker.class) { - return sTestRunnerClassName; + public String getTestRunnerClassName() { + synchronized (mLock) { + return mTestRunnerClassName; } } @@ -337,12 +268,14 @@ * memory device configuration. Should only be used for testing. * @param memoryDeviceConfig either MEMORY_DEVICE_CONFIG_LOW or MEMORY_DEVICE_CONFIG_NORMAL. */ - public static void setMemoryDeviceConfig(int memoryDeviceConfig) { - if (DEBUG) Log.i(TAG, "setMemoryDeviceConfig(" + memoryDeviceConfig + ") called"); + public void setMemoryDeviceConfig(int memoryDeviceConfig) { + if (DEBUG) { + Log.i(TAG, "setMemoryDeviceConfig(" + memoryDeviceConfig + ") called"); + } // Sanity check. This method should only be called during tests. assert NativeLibraries.sEnableLinkerTests; - synchronized (Linker.class) { - assert sMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT; + synchronized (mLock) { + assert mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT; assert memoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW || memoryDeviceConfig == MEMORY_DEVICE_CONFIG_NORMAL; if (DEBUG) { @@ -352,7 +285,7 @@ Log.i(TAG, "Simulating a regular-memory device"); } } - sMemoryDeviceConfig = memoryDeviceConfig; + mMemoryDeviceConfig = memoryDeviceConfig; } } @@ -361,130 +294,31 @@ * use this linker. If not, System.loadLibrary() should be used to load * libraries instead. */ - public static boolean isUsed() { - // Only GYP targets that are APKs and have the 'use_chromium_linker' variable - // defined as 1 will use this linker. For all others (the default), the - // auto-generated NativeLibraries.sUseLinker variable will be false. - if (!NativeLibraries.sUseLinker) return false; - - synchronized (Linker.class) { - ensureInitializedLocked(); - // At the moment, there is also no point in using this linker if the - // system does not support RELRO sharing safely. - return sRelroSharingSupported; - } - } + public abstract boolean isUsed(); /** * Call this method to determine if the linker will try to use shared RELROs * for the browser process. */ - public static boolean isUsingBrowserSharedRelros() { - synchronized (Linker.class) { - ensureInitializedLocked(); - return sBrowserUsesSharedRelro; - } - } + public abstract boolean isUsingBrowserSharedRelros(); /** * Call this method to determine if the chromium project must load * the library directly from the zip file. */ - public static boolean isInZipFile() { - return NativeLibraries.sUseLibraryInZipFile; - } + public abstract boolean isInZipFile(); /** * Call this method just before loading any native shared libraries in this process. */ - public static void prepareLibraryLoad() { - if (DEBUG) Log.i(TAG, "prepareLibraryLoad() called"); - synchronized (Linker.class) { - sPrepareLibraryLoadCalled = true; - - if (sInBrowserProcess) { - // Force generation of random base load address, as well - // as creation of shared RELRO sections in this process. - setupBaseLoadAddressLocked(); - } - } - } + public abstract void prepareLibraryLoad(); /** * Call this method just after loading all native shared libraries in this process. * Note that when in a service process, this will block until the RELRO bundle is * received, i.e. when another thread calls useSharedRelros(). */ - public static void finishLibraryLoad() { - if (DEBUG) Log.i(TAG, "finishLibraryLoad() called"); - synchronized (Linker.class) { - if (DEBUG) Log.i(TAG, String.format( - Locale.US, - "sInBrowserProcess=%s sBrowserUsesSharedRelro=%s sWaitForSharedRelros=%s", - sInBrowserProcess ? "true" : "false", - sBrowserUsesSharedRelro ? "true" : "false", - sWaitForSharedRelros ? "true" : "false")); - - if (sLoadedLibraries == null) { - if (DEBUG) Log.i(TAG, "No libraries loaded"); - } else { - if (sInBrowserProcess) { - // Create new Bundle containing RELRO section information - // for all loaded libraries. Make it available to getSharedRelros(). - sSharedRelros = createBundleFromLibInfoMap(sLoadedLibraries); - if (DEBUG) { - Log.i(TAG, "Shared RELRO created"); - dumpBundle(sSharedRelros); - } - - if (sBrowserUsesSharedRelro) { - useSharedRelrosLocked(sSharedRelros); - } - } - - if (sWaitForSharedRelros) { - assert !sInBrowserProcess; - - // Wait until the shared relro bundle is received from useSharedRelros(). - while (sSharedRelros == null) { - try { - Linker.class.wait(); - } catch (InterruptedException ie) { - // no-op - } - } - useSharedRelrosLocked(sSharedRelros); - // Clear the Bundle to ensure its file descriptor references can't be reused. - sSharedRelros.clear(); - sSharedRelros = null; - } - } - - if (NativeLibraries.sEnableLinkerTests && sTestRunnerClassName != null) { - // The TestRunner implementation must be instantiated _after_ - // all libraries are loaded to ensure that its native methods - // are properly registered. - if (DEBUG) Log.i(TAG, "Instantiating " + sTestRunnerClassName); - TestRunner testRunner = null; - try { - testRunner = (TestRunner) - Class.forName(sTestRunnerClassName).newInstance(); - } catch (Exception e) { - Log.e(TAG, "Could not extract test runner class name", e); - testRunner = null; - } - if (testRunner != null) { - if (!testRunner.runChecks(sMemoryDeviceConfig, sInBrowserProcess)) { - Log.wtf(TAG, "Linker runtime tests failed in this process!!"); - assert false; - } else { - Log.i(TAG, "All linker tests passed!"); - } - } - } - } - if (DEBUG) Log.i(TAG, "finishLibraryLoad() exiting"); - } + public abstract void finishLibraryLoad(); /** * Call this to send a Bundle containing the shared RELRO sections to be @@ -494,32 +328,7 @@ * @param bundle The Bundle instance containing a map of shared RELRO sections * to use in this process. */ - public static void useSharedRelros(Bundle bundle) { - // Ensure the bundle uses the application's class loader, not the framework - // one which doesn't know anything about LibInfo. - // Also, hold a fresh copy of it so the caller can't recycle it. - Bundle clonedBundle = null; - if (bundle != null) { - bundle.setClassLoader(LibInfo.class.getClassLoader()); - clonedBundle = new Bundle(LibInfo.class.getClassLoader()); - Parcel parcel = Parcel.obtain(); - bundle.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - clonedBundle.readFromParcel(parcel); - parcel.recycle(); - } - if (DEBUG) { - Log.i(TAG, "useSharedRelros() called with " + bundle - + ", cloned " + clonedBundle); - } - synchronized (Linker.class) { - // Note that in certain cases, this can be called before - // initServiceProcess() in service processes. - sSharedRelros = clonedBundle; - // Tell any listener blocked in finishLibraryLoad() about it. - Linker.class.notifyAll(); - } - } + public abstract void useSharedRelros(Bundle bundle); /** * Call this to retrieve the shared RELRO sections created in this process, @@ -527,33 +336,13 @@ * @return a new Bundle instance, or null if RELRO sharing is disabled on * this system, or if initServiceProcess() was called previously. */ - public static Bundle getSharedRelros() { - if (DEBUG) Log.i(TAG, "getSharedRelros() called"); - synchronized (Linker.class) { - if (!sInBrowserProcess) { - if (DEBUG) Log.i(TAG, "... returning null Bundle"); - return null; - } - - // Return the Bundle created in finishLibraryLoad(). - if (DEBUG) Log.i(TAG, "... returning " + sSharedRelros); - return sSharedRelros; - } - } - + public abstract Bundle getSharedRelros(); /** * Call this method before loading any libraries to indicate that this * process shall neither create or reuse shared RELRO sections. */ - public static void disableSharedRelros() { - if (DEBUG) Log.i(TAG, "disableSharedRelros() called"); - synchronized (Linker.class) { - sInBrowserProcess = false; - sWaitForSharedRelros = false; - sBrowserUsesSharedRelro = false; - } - } + public abstract void disableSharedRelros(); /** * Call this method before loading any libraries to indicate that this @@ -561,22 +350,7 @@ * Typically used when starting service processes. * @param baseLoadAddress the base library load address to use. */ - public static void initServiceProcess(long baseLoadAddress) { - if (DEBUG) { - Log.i(TAG, String.format( - Locale.US, "initServiceProcess(0x%x) called", baseLoadAddress)); - } - synchronized (Linker.class) { - ensureInitializedLocked(); - sInBrowserProcess = false; - sBrowserUsesSharedRelro = false; - if (sRelroSharingSupported) { - sWaitForSharedRelros = true; - sBaseLoadAddress = baseLoadAddress; - sCurrentLoadAddress = baseLoadAddress; - } - } - } + public abstract void initServiceProcess(long baseLoadAddress); /** * Retrieve the base load address of all shared RELRO sections. @@ -585,119 +359,7 @@ * @return a common, random base load address, or 0 if RELRO sharing is * disabled. */ - public static long getBaseLoadAddress() { - synchronized (Linker.class) { - ensureInitializedLocked(); - if (!sInBrowserProcess) { - Log.w(TAG, "Shared RELRO sections are disabled in this process!"); - return 0; - } - - setupBaseLoadAddressLocked(); - if (DEBUG) Log.i(TAG, String.format(Locale.US, "getBaseLoadAddress() returns 0x%x", - sBaseLoadAddress)); - return sBaseLoadAddress; - } - } - - // Used internally to lazily setup the common random base load address. - private static void setupBaseLoadAddressLocked() { - assert Thread.holdsLock(Linker.class); - if (sBaseLoadAddress == 0) { - long address = computeRandomBaseLoadAddress(); - sBaseLoadAddress = address; - sCurrentLoadAddress = address; - if (address == 0) { - // If the computed address is 0, there are issues with finding enough - // free address space, so disable RELRO shared / fixed load addresses. - Log.w(TAG, "Disabling shared RELROs due address space pressure"); - sBrowserUsesSharedRelro = false; - sWaitForSharedRelros = false; - } - } - } - - - /** - * Compute a random base load address at which to place loaded libraries. - * @return new base load address, or 0 if the system does not support - * RELRO sharing. - */ - private static long computeRandomBaseLoadAddress() { - // nativeGetRandomBaseLoadAddress() returns an address at which it has previously - // successfully mapped an area of the given size, on the basis that we will be - // able, with high probability, to map our library into it. - // - // One issue with this is that we do not yet know the size of the library that - // we will load is. So here we pass a value that we expect will always be larger - // than that needed. If it is smaller the library mapping may still succeed. The - // other issue is that although highly unlikely, there is no guarantee that - // something else does not map into the area we are going to use between here and - // when we try to map into it. - // - // The above notes mean that all of this is probablistic. It is however okay to do - // because if, worst case and unlikely, we get unlucky in our choice of address, - // the back-out and retry without the shared RELRO in the ChildProcessService will - // keep things running. - final long maxExpectedBytes = 192 * 1024 * 1024; - final long address = nativeGetRandomBaseLoadAddress(maxExpectedBytes); - if (DEBUG) { - Log.i(TAG, String.format(Locale.US, "Random native base load address: 0x%x", address)); - } - return address; - } - - // Used for debugging only. - private static void dumpBundle(Bundle bundle) { - if (DEBUG) Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle); - } - - /** - * Use the shared RELRO section from a Bundle received form another process. - * Call this after calling setBaseLoadAddress() then loading all libraries - * with loadLibrary(). - * @param bundle Bundle instance generated with createSharedRelroBundle() in - * another process. - */ - private static void useSharedRelrosLocked(Bundle bundle) { - assert Thread.holdsLock(Linker.class); - - if (DEBUG) Log.i(TAG, "Linker.useSharedRelrosLocked() called"); - - if (bundle == null) { - if (DEBUG) Log.i(TAG, "null bundle!"); - return; - } - - if (!sRelroSharingSupported) { - if (DEBUG) Log.i(TAG, "System does not support RELRO sharing"); - return; - } - - if (sLoadedLibraries == null) { - if (DEBUG) Log.i(TAG, "No libraries loaded!"); - return; - } - - if (DEBUG) dumpBundle(bundle); - HashMap<String, LibInfo> relroMap = createLibInfoMapFromBundle(bundle); - - // Apply the RELRO section to all libraries that were already loaded. - for (Map.Entry<String, LibInfo> entry : relroMap.entrySet()) { - String libName = entry.getKey(); - LibInfo libInfo = entry.getValue(); - if (!nativeUseSharedRelro(libName, libInfo)) { - Log.w(TAG, "Could not use shared RELRO section for " + libName); - } else { - if (DEBUG) Log.i(TAG, "Using shared RELRO section for " + libName); - } - } - - // In service processes, close all file descriptors from the map now. - if (!sInBrowserProcess) closeLibInfoMap(relroMap); - - if (DEBUG) Log.i(TAG, "Linker.useSharedRelrosLocked() exiting"); - } + public abstract long getBaseLoadAddress(); /** * Load a native shared library with the Chromium linker. If the zip file @@ -709,254 +371,13 @@ * @param zipFilePath The path of the zip file containing the library (or null). * @param libFilePath The path of the library (possibly in the zip file). */ - public static void loadLibrary(@Nullable String zipFilePath, String libFilePath) { - if (DEBUG) Log.i(TAG, "loadLibrary: " + zipFilePath + ", " + libFilePath); - - synchronized (Linker.class) { - ensureInitializedLocked(); - - // Security: Ensure prepareLibraryLoad() was called before. - // In theory, this can be done lazily here, but it's more consistent - // to use a pair of functions (i.e. prepareLibraryLoad() + finishLibraryLoad()) - // that wrap all calls to loadLibrary() in the library loader. - assert sPrepareLibraryLoadCalled; - - if (sLoadedLibraries == null) sLoadedLibraries = new HashMap<String, LibInfo>(); - - if (sLoadedLibraries.containsKey(libFilePath)) { - if (DEBUG) Log.i(TAG, "Not loading " + libFilePath + " twice"); - return; - } - - LibInfo libInfo = new LibInfo(); - long loadAddress = 0; - if ((sInBrowserProcess && sBrowserUsesSharedRelro) || sWaitForSharedRelros) { - // Load the library at a fixed address. - loadAddress = sCurrentLoadAddress; - } - - String sharedRelRoName = libFilePath; - if (zipFilePath != null) { - if (!nativeLoadLibraryInZipFile(zipFilePath, libFilePath, loadAddress, libInfo)) { - String errorMessage = "Unable to load library: " + libFilePath - + ", in: " + zipFilePath; - Log.e(TAG, errorMessage); - throw new UnsatisfiedLinkError(errorMessage); - } - sharedRelRoName = zipFilePath; - } else { - if (!nativeLoadLibrary(libFilePath, loadAddress, libInfo)) { - String errorMessage = "Unable to load library: " + libFilePath; - Log.e(TAG, errorMessage); - throw new UnsatisfiedLinkError(errorMessage); - } - } - - // Print the load address to the logcat when testing the linker. The format - // of the string is expected by the Python test_runner script as one of: - // BROWSER_LIBRARY_ADDRESS: <library-name> <address> - // RENDERER_LIBRARY_ADDRESS: <library-name> <address> - // Where <library-name> is the library name, and <address> is the hexadecimal load - // address. - if (NativeLibraries.sEnableLinkerTests) { - Log.i(TAG, String.format( - Locale.US, - "%s_LIBRARY_ADDRESS: %s %x", - sInBrowserProcess ? "BROWSER" : "RENDERER", - libFilePath, - libInfo.mLoadAddress)); - } - - if (sInBrowserProcess) { - // Create a new shared RELRO section at the 'current' fixed load address. - if (!nativeCreateSharedRelro(sharedRelRoName, sCurrentLoadAddress, libInfo)) { - Log.w(TAG, String.format(Locale.US, - "Could not create shared RELRO for %s at %x", libFilePath, - sCurrentLoadAddress)); - } else { - if (DEBUG) Log.i(TAG, - String.format( - Locale.US, - "Created shared RELRO for %s at %x: %s", - sharedRelRoName, - sCurrentLoadAddress, - libInfo.toString())); - } - } - - if (sCurrentLoadAddress != 0) { - // Compute the next current load address. If sBaseLoadAddress - // is not 0, this is an explicit library load address. Otherwise, - // this is an explicit load address for relocated RELRO sections - // only. - sCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize; - } - - sLoadedLibraries.put(sharedRelRoName, libInfo); - if (DEBUG) Log.i(TAG, "Library details " + libInfo.toString()); - } - } + public abstract void loadLibrary(@Nullable String zipFilePath, String libFilePath); /** * Determine whether a library is the linker library. Also deal with the * component build that adds a .cr suffix to the name. */ - public static boolean isChromiumLinkerLibrary(String library) { - return library.equals(TAG) || library.equals(TAG + ".cr"); - } - - /** - * Get the full library path in zip file (lib/<abi>/crazy.<lib_name>). - * - * @param library The library's base name. - * @return the library path. - */ - public static String getLibraryFilePathInZipFile(String library) throws FileNotFoundException { - synchronized (Linker.class) { - ensureInitializedLocked(); - - String path = nativeGetLibraryFilePathInZipFile(library); - if (path.equals("")) { - throw new FileNotFoundException( - "Failed to retrieve path in zip file for library " + library); - } - return path; - } - } - - /** - * Check whether a library is page aligned and uncompressed in the APK file. - * - * @param apkFile Filename of the APK. - * @param library The library's base name. - * @return true if page aligned and uncompressed. - */ - public static boolean checkLibraryIsMappableInApk(String apkFile, String library) { - synchronized (Linker.class) { - ensureInitializedLocked(); - - if (DEBUG) Log.i(TAG, "checkLibraryIsMappableInApk: " + apkFile + ", " + library); - boolean aligned = nativeCheckLibraryIsMappableInApk(apkFile, library); - if (DEBUG) Log.i(TAG, library + " is " + (aligned ? "" : "NOT ") - + "page aligned in " + apkFile); - return aligned; - } - } - - /** - * Move activity from the native thread to the main UI thread. - * Called from native code on its own thread. Posts a callback from - * the UI thread back to native code. - * - * @param opaque Opaque argument. - */ - @CalledByNative - public static void postCallbackOnMainThread(final long opaque) { - ThreadUtils.postOnUiThread(new Runnable() { - @Override - public void run() { - nativeRunCallbackOnUiThread(opaque); - } - }); - } - - /** - * Native method to run callbacks on the main UI thread. - * Supplied by the crazy linker and called by postCallbackOnMainThread. - * @param opaque Opaque crazy linker arguments. - */ - private static native void nativeRunCallbackOnUiThread(long opaque); - - /** - * Native method used to load a library. - * @param library Platform specific library name (e.g. libfoo.so) - * @param loadAddress Explicit load address, or 0 for randomized one. - * @param libInfo If not null, the mLoadAddress and mLoadSize fields - * of this LibInfo instance will set on success. - * @return true for success, false otherwise. - */ - private static native boolean nativeLoadLibrary(String library, - long loadAddress, - LibInfo libInfo); - - /** - * Native method used to load a library which is inside a zipfile. - * @param zipfileName Filename of the zip file containing the library. - * @param library Platform specific library name (e.g. libfoo.so) - * @param loadAddress Explicit load address, or 0 for randomized one. - * @param libInfo If not null, the mLoadAddress and mLoadSize fields - * of this LibInfo instance will set on success. - * @return true for success, false otherwise. - */ - private static native boolean nativeLoadLibraryInZipFile(String zipfileName, - String libraryName, - long loadAddress, - LibInfo libInfo); - - /** - * Native method used to create a shared RELRO section. - * If the library was already loaded at the same address using - * nativeLoadLibrary(), this creates the RELRO for it. Otherwise, - * this loads a new temporary library at the specified address, - * creates and extracts the RELRO section from it, then unloads it. - * @param library Library name. - * @param loadAddress load address, which can be different from the one - * used to load the library in the current process! - * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize - * and mRelroFd will be set. - * @return true on success, false otherwise. - */ - private static native boolean nativeCreateSharedRelro(String library, - long loadAddress, - LibInfo libInfo); - - /** - * Native method used to use a shared RELRO section. - * @param library Library name. - * @param libInfo A LibInfo instance containing valid RELRO information - * @return true on success. - */ - private static native boolean nativeUseSharedRelro(String library, - LibInfo libInfo); - - /** - * Checks that the system supports shared RELROs. Old Android kernels - * have a bug in the way they check Ashmem region protection flags, which - * makes using shared RELROs unsafe. This method performs a simple runtime - * check for this misfeature, even though nativeEnableSharedRelro() will - * always fail if this returns false. - */ - private static native boolean nativeCanUseSharedRelro(); - - /** - * Return a random address that should be free to be mapped with the given size. - * Maps an area of size bytes, and if successful then unmaps it and returns - * the address of the area allocated by the system (with ASLR). The idea is - * that this area should remain free of other mappings until we map our library - * into it. - * @param sizeBytes Size of area in bytes to search for. - * @return address to pass to future mmap, or 0 on error. - */ - private static native long nativeGetRandomBaseLoadAddress(long sizeBytes); - - /** - * Native method used to get the full library path in zip file - * (lib/<abi>/crazy.<lib_name>). - * - * @param library The library's base name. - * @return the library path (or empty string on failure). - */ - private static native String nativeGetLibraryFilePathInZipFile(String library); - - /** - * Native method which checks whether a library is page aligned and - * uncompressed in the APK file. - * - * @param apkFile Filename of the APK. - * @param library The library's base name. - * @return true if page aligned and uncompressed. - */ - private static native boolean nativeCheckLibraryIsMappableInApk(String apkFile, String library); + public abstract boolean isChromiumLinkerLibrary(String library); /** * Record information for a given library. @@ -979,7 +400,9 @@ try { ParcelFileDescriptor.adoptFd(mRelroFd).close(); } catch (java.io.IOException e) { - if (DEBUG) Log.e(TAG, "Failed to close fd: " + mRelroFd); + if (DEBUG) { + Log.e(TAG, "Failed to close fd: " + mRelroFd); + } } mRelroFd = -1; } @@ -1060,7 +483,7 @@ } // Create a Bundle from a map of LibInfo objects. - private static Bundle createBundleFromLibInfoMap(HashMap<String, LibInfo> map) { + protected Bundle createBundleFromLibInfoMap(HashMap<String, LibInfo> map) { Bundle bundle = new Bundle(map.size()); for (Map.Entry<String, LibInfo> entry : map.entrySet()) { bundle.putParcelable(entry.getKey(), entry.getValue()); @@ -1070,7 +493,7 @@ } // Create a new LibInfo map from a Bundle. - private static HashMap<String, LibInfo> createLibInfoMapFromBundle(Bundle bundle) { + protected HashMap<String, LibInfo> createLibInfoMapFromBundle(Bundle bundle) { HashMap<String, LibInfo> map = new HashMap<String, LibInfo>(); for (String library : bundle.keySet()) { LibInfo libInfo = bundle.getParcelable(library); @@ -1080,16 +503,9 @@ } // Call the close() method on all values of a LibInfo map. - private static void closeLibInfoMap(HashMap<String, LibInfo> map) { + protected void closeLibInfoMap(HashMap<String, LibInfo> map) { for (Map.Entry<String, LibInfo> entry : map.entrySet()) { entry.getValue().close(); } } - - // The map of libraries that are currently loaded in this process. - private static HashMap<String, LibInfo> sLoadedLibraries = null; - - // Used to pass the shared RELRO Bundle through Binder. - public static final String EXTRA_LINKER_SHARED_RELROS = - "org.chromium.base.android.linker.shared_relros"; }
diff --git a/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java b/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java index 24af056..1bf9c21 100644 --- a/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java +++ b/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java
@@ -20,7 +20,8 @@ @Override protected void setUp() throws Exception { super.setUp(); - LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized(); + LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER) + .ensureInitialized(getInstrumentation().getTargetContext()); RecordHistogram.initialize(); }
diff --git a/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java b/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java index f54944b..dad59ce 100644 --- a/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java +++ b/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java
@@ -125,7 +125,7 @@ // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. // Exported to C++ as: - // Java_Example_javaMethod(JNIEnv* env, jobject obj, jint foo, jint bar) + // Java_Example_javaMethod(JNIEnv* env, jobject caller, jint foo, jint bar) // Typically the C++ code would have obtained the jobject via the Init() call described above. @CalledByNative public int javaMethod(int foo, int bar) { @@ -177,7 +177,7 @@ // signatures. Besides these constraints the methods can be freely named. // This declares a C++ function which the application code must implement: - // static jint Init(JNIEnv* env, jobject obj); + // static jint Init(JNIEnv* env, jobject caller); // The jobject parameter refers back to this java side object instance. // The implementation must return the pointer to the C++ object cast to jint. // The caller of this method should store it, and supply it as a the nativeCPPClass param to @@ -195,7 +195,7 @@ private native void nativeDestroy(long nativeCPPClass); // This declares a C++ function which the application code must implement: - // static jdouble GetDoubleFunction(JNIEnv* env, jobject obj); + // static jdouble GetDoubleFunction(JNIEnv* env, jobject caller); // The jobject parameter refers back to this java side object instance. private native double nativeGetDoubleFunction(); @@ -209,7 +209,7 @@ private native void nativeSetNonPODDatatype(Rect rect); // This declares a C++ function which the application code must implement: - // static ScopedJavaLocalRef<jobject> GetNonPODDatatype(JNIEnv* env, jobject obj); + // static ScopedJavaLocalRef<jobject> GetNonPODDatatype(JNIEnv* env, jobject caller); // The jobject parameter refers back to this java side object instance. // Note that it returns a ScopedJavaLocalRef<jobject> so that you don' have to worry about // deleting the JNI local reference. This is similar with Strings and arrays.
diff --git a/android/jni_generator/jni_generator.py b/android/jni_generator/jni_generator.py index d1f14ba..74d0117 100755 --- a/android/jni_generator/jni_generator.py +++ b/android/jni_generator/jni_generator.py
@@ -236,6 +236,9 @@ 'Ljava/lang/Object', 'Ljava/lang/String', 'Ljava/lang/Class', + 'Ljava/lang/CharSequence', + 'Ljava/lang/Runnable', + 'Ljava/lang/Throwable', ] prefix = ''
diff --git a/android/jni_generator/jni_generator_tests.py b/android/jni_generator/jni_generator_tests.py index 6014847..21534ef 100755 --- a/android/jni_generator/jni_generator_tests.py +++ b/android/jni_generator/jni_generator_tests.py
@@ -1085,29 +1085,6 @@ TestOptions()) self.assertRaises(SyntaxError, willRaise) - def testImplicitImport(self): - test_data = """ - package org.chromium.android_webview; - - %(IMPORT)s - - @CalledByNative - private static void clientCertificatesCleared(Runnable callback) { - if (callbaback == null) return; - callback.run(); - } - """ - def generate(import_clause): - jni_generator.JNIFromJavaSource( - test_data % {'IMPORT': import_clause}, - 'org/chromium/android_webview/AwContentStatics', - TestOptions()) - # Ensure it raises without the import. - self.assertRaises(SyntaxError, lambda: generate('')) - - # Ensure it's fine with the import. - generate('import java.lang.Runnable;') - def testSingleJNIAdditionalImport(self): test_data = """ package org.chromium.foo;
diff --git a/android/jni_generator/sample_for_tests.cc b/android/jni_generator/sample_for_tests.cc index 3c5ca02..8ba77c6 100644 --- a/android/jni_generator/sample_for_tests.cc +++ b/android/jni_generator/sample_for_tests.cc
@@ -18,7 +18,7 @@ namespace base { namespace android { -jdouble CPPClass::InnerClass::MethodOtherP0(JNIEnv* env, jobject obj) { +jdouble CPPClass::InnerClass::MethodOtherP0(JNIEnv* env, jobject caller) { return 0.0; } @@ -33,22 +33,22 @@ return RegisterNativesImpl(env); // Generated in SampleForTests_jni.h } -void CPPClass::Destroy(JNIEnv* env, jobject obj) { +void CPPClass::Destroy(JNIEnv* env, jobject caller) { delete this; } -jint CPPClass::Method(JNIEnv* env, jobject obj) { +jint CPPClass::Method(JNIEnv* env, jobject caller) { return 0; } -void CPPClass::AddStructB(JNIEnv* env, jobject obj, jobject structb) { +void CPPClass::AddStructB(JNIEnv* env, jobject caller, jobject structb) { long key = Java_InnerStructB_getKey(env, structb); std::string value = ConvertJavaStringToUTF8( env, Java_InnerStructB_getValue(env, structb).obj()); map_[key] = value; } -void CPPClass::IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj) { +void CPPClass::IterateAndDoSomethingWithStructB(JNIEnv* env, jobject caller) { // Iterate over the elements and do something with them. for (std::map<long, std::string>::const_iterator it = map_.begin(); it != map_.end(); ++it) { @@ -59,14 +59,15 @@ } base::android::ScopedJavaLocalRef<jstring> CPPClass::ReturnAString( - JNIEnv* env, jobject obj) { + JNIEnv* env, + jobject caller) { base::android::ScopedJavaLocalRef<jstring> ret = ConvertUTF8ToJavaString( env, "test"); return ret; } // Static free functions declared and called directly from java. -static jlong Init(JNIEnv* env, jobject obj, jstring param) { +static jlong Init(JNIEnv* env, jobject caller, jstring param) { return 0; }
diff --git a/android/jni_generator/sample_for_tests.h b/android/jni_generator/sample_for_tests.h index e878e56..d183c16 100644 --- a/android/jni_generator/sample_for_tests.h +++ b/android/jni_generator/sample_for_tests.h
@@ -148,19 +148,19 @@ class InnerClass { public: - jdouble MethodOtherP0(JNIEnv* env, jobject obj); + jdouble MethodOtherP0(JNIEnv* env, jobject caller); }; - void Destroy(JNIEnv* env, jobject obj); + void Destroy(JNIEnv* env, jobject caller); - jint Method(JNIEnv* env, jobject obj); + jint Method(JNIEnv* env, jobject caller); - void AddStructB(JNIEnv* env, jobject obj, jobject structb); + void AddStructB(JNIEnv* env, jobject caller, jobject structb); - void IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj); + void IterateAndDoSomethingWithStructB(JNIEnv* env, jobject caller); - base::android::ScopedJavaLocalRef<jstring> ReturnAString( - JNIEnv* env, jobject obj); + base::android::ScopedJavaLocalRef<jstring> ReturnAString(JNIEnv* env, + jobject caller); private: std::map<long, std::string> map_;
diff --git a/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java b/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java index d3441f7..0aa9ccd 100644 --- a/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java +++ b/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java
@@ -8,7 +8,6 @@ import static org.mockito.Mockito.verify; import android.app.Activity; -import android.view.KeyEvent; import junit.framework.Assert; @@ -39,12 +38,6 @@ public void onWindowFocusChanged(@SuppressWarnings("unused") boolean hasFocus) { mWindowFocusCalls++; } - - @Implementation - public boolean dispatchKeyEvent(@SuppressWarnings("unused") KeyEvent event) { - mDispatchKeyEventCalls++; - return mReturnValueForKeyDispatch; - } } @Test @@ -65,30 +58,4 @@ // Also ensure that the original activity is forwarded the notification. Assert.assertEquals(1, shadow.mWindowFocusCalls); } - - @Test - public void testDispatchKeyEvent() throws Exception { - ActivityController<Activity> controller = - Robolectric.buildActivity(Activity.class).create().start().visible(); - TrackingShadowActivity shadow = - (TrackingShadowActivity) Robolectric.shadowOf(controller.get()); - - final KeyEvent menuKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU); - - // Ensure that key events are forwarded. - Assert.assertFalse(controller.get().getWindow().getCallback().dispatchKeyEvent(menuKey)); - // This gets called twice - once to see if the activity is swallowing it, and again to - // dispatch it. - Assert.assertEquals(2, shadow.mDispatchKeyEventCalls); - - // Ensure that our activity can swallow the event. - shadow.mReturnValueForKeyDispatch = true; - Assert.assertTrue(controller.get().getWindow().getCallback().dispatchKeyEvent(menuKey)); - Assert.assertEquals(3, shadow.mDispatchKeyEventCalls); - - // A non-enter key only dispatches once. - Assert.assertTrue(controller.get().getWindow().getCallback().dispatchKeyEvent( - new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE))); - Assert.assertEquals(4, shadow.mDispatchKeyEventCalls); - } }
diff --git a/android/junit/src/org/chromium/base/LogTest.java b/android/junit/src/org/chromium/base/LogTest.java index 46bdc67..e5ce239 100644 --- a/android/junit/src/org/chromium/base/LogTest.java +++ b/android/junit/src/org/chromium/base/LogTest.java
@@ -9,6 +9,7 @@ import static org.junit.Assert.assertTrue; import org.chromium.testing.local.LocalRobolectricTestRunner; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @@ -22,20 +23,6 @@ @RunWith(LocalRobolectricTestRunner.class) @Config(manifest = Config.NONE, shadows = {LogTest.PermissiveShadowLog.class}) public class LogTest { - /** Test method for {@link Log#makeTag(String)} */ - @Test - public void testMakeTag() { - assertEquals("cr.Foo", Log.makeTag("Foo")); - assertEquals("cr", Log.makeTag(null)); - assertEquals("cr", Log.makeTag("")); - } - - /** Test method for {@link Log#makeTag(String)} */ - @Test(expected = IllegalArgumentException.class) - public void testMakeTagFailure() { - Log.makeTag("ThisIs21Char.....Long"); - } - /** Tests that the computed call origin is the correct one. */ @Test public void callOriginTest() { @@ -88,12 +75,138 @@ assertEquals("Bar MyThrowable MyOtherThrowable", logs.get(logs.size() - 1).msg); } + public void verboseLoggingTest() { + PermissiveShadowLog.setLevel(Log.VERBOSE); + List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); + + Log.wtf("Foo", "Bar"); + Log.e("Foo", "Bar"); + Log.w("Foo", "Bar"); + Log.i("Foo", "Bar"); + Log.d("Foo", "Bar"); + Log.v("Foo", "Bar"); + + assertEquals(Log.ASSERT, logs.get(0).type); + assertEquals(Log.ERROR, logs.get(1).type); + assertEquals(Log.WARN, logs.get(2).type); + assertEquals(Log.INFO, logs.get(3).type); + assertEquals(Log.DEBUG, logs.get(4).type); + assertEquals(Log.VERBOSE, logs.get(5).type); + assertEquals(6, logs.size()); + } + + @Test + public void debugLoggingTest() { + PermissiveShadowLog.setLevel(Log.DEBUG); + List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); + + Log.wtf("Foo", "Bar"); + Log.e("Foo", "Bar"); + Log.w("Foo", "Bar"); + Log.i("Foo", "Bar"); + Log.d("Foo", "Bar"); + Log.v("Foo", "Bar"); + + assertEquals(Log.ASSERT, logs.get(0).type); + assertEquals(Log.ERROR, logs.get(1).type); + assertEquals(Log.WARN, logs.get(2).type); + assertEquals(Log.INFO, logs.get(3).type); + assertEquals(Log.DEBUG, logs.get(4).type); + assertEquals(5, logs.size()); + } + + @Test + public void infoLoggingTest() { + PermissiveShadowLog.setLevel(Log.INFO); + List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); + + Log.wtf("Foo", "Bar"); + Log.e("Foo", "Bar"); + Log.w("Foo", "Bar"); + Log.i("Foo", "Bar"); + Log.d("Foo", "Bar"); + Log.v("Foo", "Bar"); + + assertEquals(Log.ASSERT, logs.get(0).type); + assertEquals(Log.ERROR, logs.get(1).type); + assertEquals(Log.WARN, logs.get(2).type); + assertEquals(Log.INFO, logs.get(3).type); + assertEquals(4, logs.size()); + } + + @Test + public void warnLoggingTest() { + PermissiveShadowLog.setLevel(Log.WARN); + List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); + + Log.wtf("Foo", "Bar"); + Log.e("Foo", "Bar"); + Log.w("Foo", "Bar"); + Log.i("Foo", "Bar"); + Log.d("Foo", "Bar"); + Log.v("Foo", "Bar"); + + assertEquals(Log.ASSERT, logs.get(0).type); + assertEquals(Log.ERROR, logs.get(1).type); + assertEquals(Log.WARN, logs.get(2).type); + assertEquals(3, logs.size()); + } + + @Test + public void errorLoggingTest() { + PermissiveShadowLog.setLevel(Log.ERROR); + List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); + + Log.wtf("Foo", "Bar"); + Log.e("Foo", "Bar"); + Log.w("Foo", "Bar"); + Log.i("Foo", "Bar"); + Log.d("Foo", "Bar"); + Log.v("Foo", "Bar"); + + assertEquals(Log.ASSERT, logs.get(0).type); + assertEquals(Log.ERROR, logs.get(1).type); + assertEquals(2, logs.size()); + } + + @Test + public void assertLoggingTest() { + PermissiveShadowLog.setLevel(Log.ASSERT); + List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); + + Log.wtf("Foo", "Bar"); + Log.e("Foo", "Bar"); + Log.w("Foo", "Bar"); + Log.i("Foo", "Bar"); + Log.d("Foo", "Bar"); + Log.v("Foo", "Bar"); + + assertEquals(Log.ASSERT, logs.get(0).type); + assertEquals(1, logs.size()); + } + + @Before + public void beforeTest() { + PermissiveShadowLog.reset(); + } + /** Needed to allow debug/verbose logging that is disabled by default. */ @Implements(android.util.Log.class) public static class PermissiveShadowLog extends ShadowLog { + private static int sLevel = Log.VERBOSE; + + /** Sets the log level for all tags. */ + public static void setLevel(int level) { + sLevel = level; + } + @Implementation public static boolean isLoggable(String tag, int level) { - return true; + return level >= sLevel; + } + + public static void reset() { + sLevel = Log.VERBOSE; } } }
diff --git a/android/library_loader/library_loader_hooks.cc b/android/library_loader/library_loader_hooks.cc index 0b59a30..0313f70 100644 --- a/android/library_loader/library_loader_hooks.cc +++ b/android/library_loader/library_loader_hooks.cc
@@ -48,13 +48,19 @@ RendererHistogramCode g_renderer_histogram_code = NO_PENDING_HISTOGRAM_CODE; +// The amount of time, in milliseconds, that it took to load the shared +// libraries in the renderer. Set in +// RegisterChromiumAndroidLinkerRendererHistogram. +long g_renderer_library_load_time_ms = 0; + } // namespace static void RegisterChromiumAndroidLinkerRendererHistogram( JNIEnv* env, jobject jcaller, jboolean requested_shared_relro, - jboolean load_at_fixed_address_failed) { + jboolean load_at_fixed_address_failed, + jlong library_load_time_ms) { // Note a pending histogram value for later recording. if (requested_shared_relro) { g_renderer_histogram_code = load_at_fixed_address_failed @@ -62,6 +68,8 @@ } else { g_renderer_histogram_code = LFA_NOT_ATTEMPTED; } + + g_renderer_library_load_time_ms = library_load_time_ms; } void RecordChromiumAndroidLinkerRendererHistogram() { @@ -72,6 +80,11 @@ g_renderer_histogram_code, MAX_RENDERER_HISTOGRAM_CODE); g_renderer_histogram_code = NO_PENDING_HISTOGRAM_CODE; + + // Record how long it took to load the shared libraries. + UMA_HISTOGRAM_TIMES( + "ChromiumAndroidLinker.RendererLoadTime", + base::TimeDelta::FromMilliseconds(g_renderer_library_load_time_ms)); } static void RecordChromiumAndroidLinkerBrowserHistogram( @@ -79,7 +92,8 @@ jobject jcaller, jboolean is_using_browser_shared_relros, jboolean load_at_fixed_address_failed, - jint library_load_from_apk_status) { + jint library_load_from_apk_status, + jlong library_load_time_ms) { // For low-memory devices, record whether or not we successfully loaded the // browser at a fixed address. Otherwise just record a normal invocation. BrowserHistogramCode histogram_code; @@ -97,6 +111,10 @@ UMA_HISTOGRAM_ENUMERATION("ChromiumAndroidLinker.LibraryLoadFromApkStatus", library_load_from_apk_status, LIBRARY_LOAD_FROM_APK_STATUS_CODES_MAX); + + // Record how long it took to load the shared libraries. + UMA_HISTOGRAM_TIMES("ChromiumAndroidLinker.BrowserLoadTime", + base::TimeDelta::FromMilliseconds(library_load_time_ms)); } void SetLibraryLoadedHook(LibraryLoadedHook* func) {
diff --git a/android/library_loader/library_prefetcher.cc b/android/library_loader/library_prefetcher.cc index 798a283..9b54843 100644 --- a/android/library_loader/library_prefetcher.cc +++ b/android/library_loader/library_prefetcher.cc
@@ -36,7 +36,7 @@ bool PathMatchesSuffix(const std::string& path) { for (size_t i = 0; i < arraysize(kSuffixesToMatch); i++) { - if (EndsWith(path, kSuffixesToMatch[i], true)) { + if (EndsWith(path, kSuffixesToMatch[i], CompareCase::SENSITIVE)) { return true; } } @@ -82,14 +82,14 @@ std::vector<AddressRange>* ranges) { bool has_libchrome_region = false; for (const base::debug::MappedMemoryRegion& region : regions) { - if (EndsWith(region.path, kLibchromeSuffix, true)) { + if (EndsWith(region.path, kLibchromeSuffix, CompareCase::SENSITIVE)) { has_libchrome_region = true; break; } } for (const base::debug::MappedMemoryRegion& region : regions) { if (has_libchrome_region && - !EndsWith(region.path, kLibchromeSuffix, true)) { + !EndsWith(region.path, kLibchromeSuffix, CompareCase::SENSITIVE)) { continue; } ranges->push_back(std::make_pair(region.start, region.end));
diff --git a/android/linker/BUILD.gn b/android/linker/BUILD.gn index 190ea47..043bfc6 100644 --- a/android/linker/BUILD.gn +++ b/android/linker/BUILD.gn
@@ -9,7 +9,7 @@ # GYP: //base/base.gyp:chromium_android_linker shared_library("chromium_android_linker") { sources = [ - "linker_jni.cc", + "legacy_linker_jni.cc", ] # The NDK contains the crazy_linker here:
diff --git a/android/linker/linker_jni.cc b/android/linker/legacy_linker_jni.cc similarity index 75% rename from android/linker/linker_jni.cc rename to android/linker/legacy_linker_jni.cc index 2bc480c..4612fac 100644 --- a/android/linker/linker_jni.cc +++ b/android/linker/legacy_linker_jni.cc
@@ -22,6 +22,13 @@ #include <sys/mman.h> #include <unistd.h> +// See commentary in crazy_linker_elf_loader.cpp for the effect of setting +// this. If changing there, change here also. +// +// For more, see: +// https://crbug.com/504410 +#define RESERVE_BREAKPAD_GUARD_REGION 1 + // Set this to 1 to enable debug traces to the Android log. // Note that LOG() from "base/logging.h" cannot be used, since it is // in base/ which hasn't been loaded yet. @@ -104,8 +111,8 @@ LOG_ERROR("Could not find ID for field '%s'", field_name); return false; } - LOG_INFO( - "%s: Found ID %p for field '%s'", __FUNCTION__, *field_id, field_name); + LOG_INFO("%s: Found ID %p for field '%s'", __FUNCTION__, *field_id, + field_name); return true; } @@ -123,8 +130,8 @@ LOG_ERROR("Could not find ID for static method '%s'", method_name); return false; } - LOG_INFO("%s: Found ID %p for static method '%s'", - __FUNCTION__, *method_id, method_name); + LOG_INFO("%s: Found ID %p for static method '%s'", __FUNCTION__, *method_id, + method_name); return true; } @@ -142,9 +149,8 @@ LOG_ERROR("Could not find ID for static field '%s'", field_name); return false; } - LOG_INFO( - "%s: Found ID %p for static field '%s'", - __FUNCTION__, *field_id, field_name); + LOG_INFO("%s: Found ID %p for static field '%s'", __FUNCTION__, *field_id, + field_name); return true; } @@ -165,9 +171,8 @@ return false; *value = env->GetStaticIntField(clazz, field_id); - LOG_INFO( - "%s: Found value %d for class '%s', static field '%s'", - __FUNCTION__, *value, class_name, field_name); + LOG_INFO("%s: Found value %d for class '%s', static field '%s'", __FUNCTION__, + *value, class_name, field_name); return true; } @@ -186,7 +191,7 @@ bool Init(JNIEnv* env) { jclass clazz; if (!InitClassReference( - env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) { + env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) { return false; } @@ -245,8 +250,8 @@ return false; crazy_set_sdk_build_version(static_cast<int>(value)); - LOG_INFO("%s: Set SDK build version to %d", - __FUNCTION__, static_cast<int>(value)); + LOG_INFO("%s: Set SDK build version to %d", __FUNCTION__, + static_cast<int>(value)); return true; } @@ -298,10 +303,11 @@ namespace { template <class LibraryOpener> -bool GenericLoadLibrary( - JNIEnv* env, - const char* library_name, jlong load_address, jobject lib_info_obj, - const LibraryOpener& opener) { +bool GenericLoadLibrary(JNIEnv* env, + const char* library_name, + jlong load_address, + jobject lib_info_obj, + const LibraryOpener& opener) { crazy_context_t* context = GetCrazyContext(); if (!IsValidAddress(load_address)) { @@ -319,18 +325,16 @@ crazy_library_info_t info; if (!crazy_library_get_info(library.Get(), context, &info)) { - LOG_ERROR("%s: Could not get library information for %s: %s", - __FUNCTION__, - library_name, - crazy_context_get_error(context)); + LOG_ERROR("%s: Could not get library information for %s: %s", __FUNCTION__, + library_name, crazy_context_get_error(context)); return false; } // Release library object to keep it alive after the function returns. library.Release(); - s_lib_info_fields.SetLoadInfo( - env, lib_info_obj, info.load_address, info.load_size); + s_lib_info_fields.SetLoadInfo(env, lib_info_obj, info.load_address, + info.load_size); LOG_INFO("%s: Success loading library %s", __FUNCTION__, library_name); return true; } @@ -338,20 +342,16 @@ // Used for opening the library in a regular file. class FileLibraryOpener { public: - bool Open( - crazy_library_t** library, - const char* library_name, - crazy_context_t* context) const; + bool Open(crazy_library_t** library, + const char* library_name, + crazy_context_t* context) const; }; -bool FileLibraryOpener::Open( - crazy_library_t** library, - const char* library_name, - crazy_context_t* context) const { +bool FileLibraryOpener::Open(crazy_library_t** library, + const char* library_name, + crazy_context_t* context) const { if (!crazy_library_open(library, library_name, context)) { - LOG_ERROR("%s: Could not open %s: %s", - __FUNCTION__, - library_name, + LOG_ERROR("%s: Could not open %s: %s", __FUNCTION__, library_name, crazy_context_get_error(context)); return false; } @@ -362,24 +362,22 @@ class ZipLibraryOpener { public: explicit ZipLibraryOpener(const char* zip_file) : zip_file_(zip_file) {} - bool Open( - crazy_library_t** library, - const char* library_name, - crazy_context_t* context) const; + bool Open(crazy_library_t** library, + const char* library_name, + crazy_context_t* context) const; + private: const char* zip_file_; }; -bool ZipLibraryOpener::Open( - crazy_library_t** library, - const char* library_name, - crazy_context_t* context) const { - if (!crazy_library_open_in_zip_file( - library, zip_file_, library_name, context)) { - LOG_ERROR("%s: Could not open %s in zip file %s: %s", - __FUNCTION__, library_name, zip_file_, - crazy_context_get_error(context)); - return false; +bool ZipLibraryOpener::Open(crazy_library_t** library, + const char* library_name, + crazy_context_t* context) const { + if (!crazy_library_open_in_zip_file(library, zip_file_, library_name, + context)) { + LOG_ERROR("%s: Could not open %s in zip file %s: %s", __FUNCTION__, + library_name, zip_file_, crazy_context_get_error(context)); + return false; } return true; } @@ -406,9 +404,9 @@ jobject lib_info_obj) { String lib_name(env, library_name); FileLibraryOpener opener; - return GenericLoadLibrary( - env, lib_name.c_str(), - static_cast<size_t>(load_address), lib_info_obj, opener); + return GenericLoadLibrary(env, lib_name.c_str(), + static_cast<size_t>(load_address), lib_info_obj, + opener); } // Load a library from a zipfile with the chromium linker. The @@ -442,9 +440,9 @@ String zipfile_name_str(env, zipfile_name); String lib_name(env, library_name); ZipLibraryOpener opener(zipfile_name_str.c_str()); - return GenericLoadLibrary( - env, lib_name.c_str(), - static_cast<size_t>(load_address), lib_info_obj, opener); + return GenericLoadLibrary(env, lib_name.c_str(), + static_cast<size_t>(load_address), lib_info_obj, + opener); } // Class holding the Java class and method ID for the Java side Linker @@ -456,11 +454,8 @@ // Initialize an instance. bool Init(JNIEnv* env, jclass linker_class) { clazz = reinterpret_cast<jclass>(env->NewGlobalRef(linker_class)); - return InitStaticMethodId(env, - linker_class, - "postCallbackOnMainThread", - "(J)V", - &method_id); + return InitStaticMethodId(env, linker_class, "postCallbackOnMainThread", + "(J)V", &method_id); } }; @@ -475,8 +470,8 @@ void RunCallbackOnUiThread(JNIEnv* env, jclass clazz, jlong arg) { crazy_callback_t* callback = reinterpret_cast<crazy_callback_t*>(arg); - LOG_INFO("%s: Called back from java with handler %p, opaque %p", - __FUNCTION__, callback->handler, callback->opaque); + LOG_INFO("%s: Called back from java with handler %p, opaque %p", __FUNCTION__, + callback->handler, callback->opaque); crazy_callback_run(callback); delete callback; @@ -496,14 +491,13 @@ JavaVM* vm; int minimum_jni_version; - crazy_context_get_java_vm(context, - reinterpret_cast<void**>(&vm), + crazy_context_get_java_vm(context, reinterpret_cast<void**>(&vm), &minimum_jni_version); // Do not reuse JNIEnv from JNI_OnLoad, but retrieve our own. JNIEnv* env; - if (JNI_OK != vm->GetEnv( - reinterpret_cast<void**>(&env), minimum_jni_version)) { + if (JNI_OK != + vm->GetEnv(reinterpret_cast<void**>(&env), minimum_jni_version)) { LOG_ERROR("Could not create JNIEnv"); return false; } @@ -512,13 +506,13 @@ crazy_callback_t* callback = new crazy_callback_t(); *callback = *callback_request; - LOG_INFO("%s: Calling back to java with handler %p, opaque %p", - __FUNCTION__, callback->handler, callback->opaque); + LOG_INFO("%s: Calling back to java with handler %p, opaque %p", __FUNCTION__, + callback->handler, callback->opaque); jlong arg = static_cast<jlong>(reinterpret_cast<uintptr_t>(callback)); - env->CallStaticVoidMethod( - s_java_callback_bindings.clazz, s_java_callback_bindings.method_id, arg); + env->CallStaticVoidMethod(s_java_callback_bindings.clazz, + s_java_callback_bindings.method_id, arg); // Back out and return false if we encounter a JNI exception. if (env->ExceptionCheck() == JNI_TRUE) { @@ -556,21 +550,16 @@ size_t relro_size = 0; int relro_fd = -1; - if (!crazy_library_create_shared_relro(library.Get(), - context, - static_cast<size_t>(load_address), - &relro_start, - &relro_size, - &relro_fd)) { + if (!crazy_library_create_shared_relro( + library.Get(), context, static_cast<size_t>(load_address), + &relro_start, &relro_size, &relro_fd)) { LOG_ERROR("%s: Could not create shared RELRO sharing for %s: %s\n", - __FUNCTION__, - lib_name.c_str(), - crazy_context_get_error(context)); + __FUNCTION__, lib_name.c_str(), crazy_context_get_error(context)); return false; } - s_lib_info_fields.SetRelroInfo( - env, lib_info_obj, relro_start, relro_size, relro_fd); + s_lib_info_fields.SetRelroInfo(env, lib_info_obj, relro_start, relro_size, + relro_fd); return true; } @@ -580,9 +569,7 @@ jobject lib_info_obj) { String lib_name(env, library_name); - LOG_INFO("%s: called for %s, lib_info_ref=%p", - __FUNCTION__, - lib_name.c_str(), + LOG_INFO("%s: called for %s, lib_info_ref=%p", __FUNCTION__, lib_name.c_str(), lib_info_obj); ScopedLibrary library; @@ -595,27 +582,20 @@ size_t relro_start = 0; size_t relro_size = 0; int relro_fd = -1; - s_lib_info_fields.GetRelroInfo( - env, lib_info_obj, &relro_start, &relro_size, &relro_fd); + s_lib_info_fields.GetRelroInfo(env, lib_info_obj, &relro_start, &relro_size, + &relro_fd); - LOG_INFO("%s: library=%s relro start=%p size=%p fd=%d", - __FUNCTION__, - lib_name.c_str(), - (void*)relro_start, - (void*)relro_size, - relro_fd); + LOG_INFO("%s: library=%s relro start=%p size=%p fd=%d", __FUNCTION__, + lib_name.c_str(), (void*)relro_start, (void*)relro_size, relro_fd); - if (!crazy_library_use_shared_relro( - library.Get(), context, relro_start, relro_size, relro_fd)) { - LOG_ERROR("%s: Could not use shared RELRO for %s: %s", - __FUNCTION__, - lib_name.c_str(), - crazy_context_get_error(context)); + if (!crazy_library_use_shared_relro(library.Get(), context, relro_start, + relro_size, relro_fd)) { + LOG_ERROR("%s: Could not use shared RELRO for %s: %s", __FUNCTION__, + lib_name.c_str(), crazy_context_get_error(context)); return false; } - LOG_INFO("%s: Library %s using shared RELRO section!", - __FUNCTION__, + LOG_INFO("%s: Library %s using shared RELRO section!", __FUNCTION__, lib_name.c_str()); return true; @@ -626,6 +606,13 @@ } jlong GetRandomBaseLoadAddress(JNIEnv* env, jclass clazz, jlong bytes) { +#if RESERVE_BREAKPAD_GUARD_REGION + // Add a Breakpad guard region. 16Mb should be comfortably larger than + // the largest relocation packer saving we expect to encounter. + static const size_t kBreakpadGuardRegionBytes = 16 * 1024 * 1024; + bytes += kBreakpadGuardRegionBytes; +#endif + void* address = mmap(NULL, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (address == MAP_FAILED) { @@ -633,56 +620,17 @@ return 0; } munmap(address, bytes); + +#if RESERVE_BREAKPAD_GUARD_REGION + // Allow for a Breakpad guard region ahead of the returned address. + address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) + + kBreakpadGuardRegionBytes); +#endif + LOG_INFO("%s: Random base load address is %p\n", __FUNCTION__, address); return static_cast<jlong>(reinterpret_cast<uintptr_t>(address)); } -// Get the full path of a library in the zip file -// (lib/<abi>/crazy.<lib_name>). -// -// |env| is the current JNI environment handle. -// |clazz| is the static class handle which is not used here. -// |lib_name| is the library base name. -// Returns the full path (or empty string on failure). -jstring GetLibraryFilePathInZipFile(JNIEnv* env, - jclass clazz, - jstring lib_name) { - String lib_name_str(env, lib_name); - const char* lib_name_c_str = lib_name_str.c_str(); - char buffer[kMaxFilePathLengthInZip + 1]; - if (crazy_library_file_path_in_zip_file( - lib_name_c_str, buffer, sizeof(buffer)) == CRAZY_STATUS_FAILURE) { - LOG_ERROR("%s: Failed to get full filename for library '%s'", - __FUNCTION__, lib_name_c_str); - buffer[0] = '\0'; - } - return env->NewStringUTF(buffer); -} - -// Check whether a library is page aligned and uncompressed in the APK file. -// -// |env| is the current JNI environment handle. -// |clazz| is the static class handle which is not used here. -// |apkfile_name| is the filename of the APK. -// |library_name| is the library base name. -// Returns true if page aligned and uncompressed. -jboolean CheckLibraryIsMappableInApk(JNIEnv* env, jclass clazz, - jstring apkfile_name, - jstring library_name) { - String apkfile_name_str(env, apkfile_name); - const char* apkfile_name_c_str = apkfile_name_str.c_str(); - String library_name_str(env, library_name); - const char* library_name_c_str = library_name_str.c_str(); - - LOG_INFO("%s: Checking if %s is page-aligned and uncompressed in %s\n", - __FUNCTION__, library_name_c_str, apkfile_name_c_str); - jboolean mappable = crazy_linker_check_library_is_mappable_in_zip_file( - apkfile_name_c_str, library_name_c_str) == CRAZY_STATUS_SUCCESS; - LOG_INFO("%s: %s\n", __FUNCTION__, mappable ? "Mappable" : "NOT mappable"); - - return mappable; -} - const JNINativeMethod kNativeMethods[] = { {"nativeLoadLibrary", "(" @@ -733,19 +681,7 @@ ")" "J", reinterpret_cast<void*>(&GetRandomBaseLoadAddress)}, - {"nativeGetLibraryFilePathInZipFile", - "(" - "Ljava/lang/String;" - ")" - "Ljava/lang/String;", - reinterpret_cast<void*>(&GetLibraryFilePathInZipFile)}, - {"nativeCheckLibraryIsMappableInApk", - "(" - "Ljava/lang/String;" - "Ljava/lang/String;" - ")" - "Z", - reinterpret_cast<void*>(&CheckLibraryIsMappableInApk)}, }; +}; } // namespace @@ -768,14 +704,12 @@ // Register native methods. jclass linker_class; - if (!InitClassReference(env, - "org/chromium/base/library_loader/Linker", + if (!InitClassReference(env, "org/chromium/base/library_loader/LegacyLinker", &linker_class)) return -1; LOG_INFO("%s: Registering native methods", __FUNCTION__); - env->RegisterNatives(linker_class, - kNativeMethods, + env->RegisterNatives(linker_class, kNativeMethods, sizeof(kNativeMethods) / sizeof(kNativeMethods[0])); // Find LibInfo field ids.
diff --git a/android/path_utils.cc b/android/path_utils.cc index c98007c..caad53a 100644 --- a/android/path_utils.cc +++ b/android/path_utils.cc
@@ -41,6 +41,16 @@ return true; } +bool GetThumbnailCacheDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getThumbnailCacheDirectoryPath(env, + GetApplicationContext()); + FilePath thumbnail_cache_path(ConvertJavaStringToUTF8(path)); + *result = thumbnail_cache_path; + return true; +} + bool GetDownloadsDirectory(FilePath* result) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jstring> path =
diff --git a/android/path_utils.h b/android/path_utils.h index d3421b3..6501f1b 100644 --- a/android/path_utils.h +++ b/android/path_utils.h
@@ -31,6 +31,10 @@ // cache dir. BASE_EXPORT bool GetCacheDirectory(FilePath* result); +// Retrieves the path to the thumbnail cache directory. The result is placed +// in the FilePath pointed to by 'result'. +BASE_EXPORT bool GetThumbnailCacheDirectory(FilePath* result); + // Retrieves the path to the public downloads directory. The result is placed // in the FilePath pointed to by 'result'. BASE_EXPORT bool GetDownloadsDirectory(FilePath* result);
diff --git a/android/trace_event_binding.cc b/android/trace_event_binding.cc index 791b67f..3c5ee17 100644 --- a/android/trace_event_binding.cc +++ b/android/trace_event_binding.cc
@@ -8,6 +8,7 @@ #include <set> +#include "base/android/jni_string.h" #include "base/lazy_instance.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event_impl.h" @@ -25,32 +26,28 @@ // Boilerplate for safely converting Java data to TRACE_EVENT data. class TraceEventDataConverter { public: - TraceEventDataConverter(JNIEnv* env, - jstring jname, - jstring jarg) + TraceEventDataConverter(JNIEnv* env, jstring jname, jstring jarg) : env_(env), jname_(jname), jarg_(jarg), - name_(env->GetStringUTFChars(jname, NULL)), - arg_(jarg ? env->GetStringUTFChars(jarg, NULL) : NULL) { - } + name_(ConvertJavaStringToUTF8(env, jname)), + has_arg_(jarg != nullptr), + arg_(jarg ? ConvertJavaStringToUTF8(env, jarg) : "") {} ~TraceEventDataConverter() { - env_->ReleaseStringUTFChars(jname_, name_); - if (jarg_) - env_->ReleaseStringUTFChars(jarg_, arg_); } // Return saves values to pass to TRACE_EVENT macros. - const char* name() { return name_; } - const char* arg_name() { return arg_ ? "arg" : NULL; } - const char* arg() { return arg_; } + const char* name() { return name_.c_str(); } + const char* arg_name() { return has_arg_ ? "arg" : nullptr; } + const char* arg() { return has_arg_ ? arg_.c_str() : nullptr; } private: JNIEnv* env_; jstring jname_; jstring jarg_; - const char* name_; - const char* arg_; + std::string name_; + bool has_arg_; + std::string arg_; DISALLOW_COPY_AND_ASSIGN(TraceEventDataConverter); };
diff --git a/atomicops.h b/atomicops.h index 6a5371c..f983b45 100644 --- a/atomicops.h +++ b/atomicops.h
@@ -144,33 +144,6 @@ } // namespace subtle } // namespace base -// The following x86 CPU features are used in atomicops_internals_x86_gcc.h, but -// this file is duplicated inside of Chrome: protobuf and tcmalloc rely on the -// struct being present at link time. Some parts of Chrome can currently use the -// portable interface whereas others still use GCC one. The include guards are -// the same as in atomicops_internals_x86_gcc.cc. -#if defined(__i386__) || defined(__x86_64__) -// This struct is not part of the public API of this module; clients may not -// use it. (However, it's exported via BASE_EXPORT because clients implicitly -// do use it at link time by inlining these functions.) -// Features of this x86. Values may not be correct before main() is run, -// but are set conservatively. -struct AtomicOps_x86CPUFeatureStruct { - bool has_amd_lock_mb_bug; // Processor has AMD memory-barrier bug; do lfence - // after acquire compare-and-swap. - // The following fields are unused by Chrome's base implementation but are - // still used by copies of the same code in other parts of the code base. This - // causes an ODR violation, and the other code is likely reading invalid - // memory. - // TODO(jfb) Delete these fields once the rest of the Chrome code base doesn't - // depend on them. - bool has_sse2; // Processor has SSE2. - bool has_cmpxchg16b; // Processor supports cmpxchg16b instruction. -}; -BASE_EXPORT extern struct AtomicOps_x86CPUFeatureStruct - AtomicOps_Internalx86CPUFeatures; -#endif - // Try to use a portable implementation based on C++11 atomics. // // Some toolchains support C++11 language features without supporting library @@ -188,17 +161,6 @@ # include "base/atomicops_internals_x86_msvc.h" # elif defined(OS_MACOSX) # include "base/atomicops_internals_mac.h" -# elif defined(OS_NACL) -# include "base/atomicops_internals_gcc.h" -# elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARMEL) -# include "base/atomicops_internals_arm_gcc.h" -# elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARM64) -# include "base/atomicops_internals_arm64_gcc.h" -# elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY) -# include "base/atomicops_internals_x86_gcc.h" -# elif (defined(COMPILER_GCC) && \ - (defined(ARCH_CPU_MIPS_FAMILY) || defined(ARCH_CPU_MIPS64_FAMILY))) -# include "base/atomicops_internals_mips_gcc.h" # else # error "Atomic operations are not supported on your platform" # endif
diff --git a/atomicops_internals_arm64_gcc.h b/atomicops_internals_arm64_gcc.h deleted file mode 100644 index ddcfec9..0000000 --- a/atomicops_internals_arm64_gcc.h +++ /dev/null
@@ -1,307 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file is an internal atomic implementation, use base/atomicops.h instead. - -// TODO(rmcilroy): Investigate whether we can use __sync__ intrinsics instead of -// the hand coded assembly without introducing perf regressions. -// TODO(rmcilroy): Investigate whether we can use acquire / release versions of -// exclusive load / store assembly instructions and do away with -// the barriers. - -#ifndef BASE_ATOMICOPS_INTERNALS_ARM64_GCC_H_ -#define BASE_ATOMICOPS_INTERNALS_ARM64_GCC_H_ - -#if defined(OS_QNX) -#include <sys/cpuinline.h> -#endif - -namespace base { -namespace subtle { - -inline void MemoryBarrier() { - __asm__ __volatile__ ("dmb ish" ::: "memory"); // NOLINT -} - -// NoBarrier versions of the operation include "memory" in the clobber list. -// This is not required for direct usage of the NoBarrier versions of the -// operations. However this is required for correctness when they are used as -// part of the Acquire or Release versions, to ensure that nothing from outside -// the call is reordered between the operation and the memory barrier. This does -// not change the code generated, so has no or minimal impact on the -// NoBarrier operations. - -inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev; - int32_t temp; - - __asm__ __volatile__ ( // NOLINT - "0: \n\t" - "ldxr %w[prev], %[ptr] \n\t" // Load the previous value. - "cmp %w[prev], %w[old_value] \n\t" - "bne 1f \n\t" - "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. - "cbnz %w[temp], 0b \n\t" // Retry if it did not work. - "1: \n\t" - : [prev]"=&r" (prev), - [temp]"=&r" (temp), - [ptr]"+Q" (*ptr) - : [old_value]"IJr" (old_value), - [new_value]"r" (new_value) - : "cc", "memory" - ); // NOLINT - - return prev; -} - -inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, - Atomic32 new_value) { - Atomic32 result; - int32_t temp; - - __asm__ __volatile__ ( // NOLINT - "0: \n\t" - "ldxr %w[result], %[ptr] \n\t" // Load the previous value. - "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. - "cbnz %w[temp], 0b \n\t" // Retry if it did not work. - : [result]"=&r" (result), - [temp]"=&r" (temp), - [ptr]"+Q" (*ptr) - : [new_value]"r" (new_value) - : "memory" - ); // NOLINT - - return result; -} - -inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - Atomic32 result; - int32_t temp; - - __asm__ __volatile__ ( // NOLINT - "0: \n\t" - "ldxr %w[result], %[ptr] \n\t" // Load the previous value. - "add %w[result], %w[result], %w[increment]\n\t" - "stxr %w[temp], %w[result], %[ptr] \n\t" // Try to store the result. - "cbnz %w[temp], 0b \n\t" // Retry on failure. - : [result]"=&r" (result), - [temp]"=&r" (temp), - [ptr]"+Q" (*ptr) - : [increment]"IJr" (increment) - : "memory" - ); // NOLINT - - return result; -} - -inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - MemoryBarrier(); - Atomic32 result = NoBarrier_AtomicIncrement(ptr, increment); - MemoryBarrier(); - - return result; -} - -inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - MemoryBarrier(); - - return prev; -} - -inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - MemoryBarrier(); - Atomic32 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - - return prev; -} - -inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; -} - -inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { - __asm__ __volatile__ ( // NOLINT - "stlr %w[value], %[ptr] \n\t" - : [ptr]"=Q" (*ptr) - : [value]"r" (value) - : "memory" - ); // NOLINT -} - -inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { - return *ptr; -} - -inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { - Atomic32 value; - - __asm__ __volatile__ ( // NOLINT - "ldar %w[value], %[ptr] \n\t" - : [value]"=r" (value) - : [ptr]"Q" (*ptr) - : "memory" - ); // NOLINT - - return value; -} - -inline Atomic32 Release_Load(volatile const Atomic32* ptr) { - MemoryBarrier(); - return *ptr; -} - -// 64-bit versions of the operations. -// See the 32-bit versions for comments. - -inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - Atomic64 prev; - int32_t temp; - - __asm__ __volatile__ ( // NOLINT - "0: \n\t" - "ldxr %[prev], %[ptr] \n\t" - "cmp %[prev], %[old_value] \n\t" - "bne 1f \n\t" - "stxr %w[temp], %[new_value], %[ptr] \n\t" - "cbnz %w[temp], 0b \n\t" - "1: \n\t" - : [prev]"=&r" (prev), - [temp]"=&r" (temp), - [ptr]"+Q" (*ptr) - : [old_value]"IJr" (old_value), - [new_value]"r" (new_value) - : "cc", "memory" - ); // NOLINT - - return prev; -} - -inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, - Atomic64 new_value) { - Atomic64 result; - int32_t temp; - - __asm__ __volatile__ ( // NOLINT - "0: \n\t" - "ldxr %[result], %[ptr] \n\t" - "stxr %w[temp], %[new_value], %[ptr] \n\t" - "cbnz %w[temp], 0b \n\t" - : [result]"=&r" (result), - [temp]"=&r" (temp), - [ptr]"+Q" (*ptr) - : [new_value]"r" (new_value) - : "memory" - ); // NOLINT - - return result; -} - -inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, - Atomic64 increment) { - Atomic64 result; - int32_t temp; - - __asm__ __volatile__ ( // NOLINT - "0: \n\t" - "ldxr %[result], %[ptr] \n\t" - "add %[result], %[result], %[increment] \n\t" - "stxr %w[temp], %[result], %[ptr] \n\t" - "cbnz %w[temp], 0b \n\t" - : [result]"=&r" (result), - [temp]"=&r" (temp), - [ptr]"+Q" (*ptr) - : [increment]"IJr" (increment) - : "memory" - ); // NOLINT - - return result; -} - -inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, - Atomic64 increment) { - MemoryBarrier(); - Atomic64 result = NoBarrier_AtomicIncrement(ptr, increment); - MemoryBarrier(); - - return result; -} - -inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - Atomic64 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - MemoryBarrier(); - - return prev; -} - -inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - MemoryBarrier(); - Atomic64 prev = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - - return prev; -} - -inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { - *ptr = value; -} - -inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { - __asm__ __volatile__ ( // NOLINT - "stlr %x[value], %[ptr] \n\t" - : [ptr]"=Q" (*ptr) - : [value]"r" (value) - : "memory" - ); // NOLINT -} - -inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { - return *ptr; -} - -inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { - Atomic64 value; - - __asm__ __volatile__ ( // NOLINT - "ldar %x[value], %[ptr] \n\t" - : [value]"=r" (value) - : [ptr]"Q" (*ptr) - : "memory" - ); // NOLINT - - return value; -} - -inline Atomic64 Release_Load(volatile const Atomic64* ptr) { - MemoryBarrier(); - return *ptr; -} - -} // namespace subtle -} // namespace base - -#endif // BASE_ATOMICOPS_INTERNALS_ARM64_GCC_H_
diff --git a/atomicops_internals_arm_gcc.h b/atomicops_internals_arm_gcc.h deleted file mode 100644 index 44c91c8..0000000 --- a/atomicops_internals_arm_gcc.h +++ /dev/null
@@ -1,294 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file is an internal atomic implementation, use base/atomicops.h instead. -// -// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears. - -#ifndef BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_ -#define BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_ - -#if defined(OS_QNX) -#include <sys/cpuinline.h> -#endif - -namespace base { -namespace subtle { - -// Memory barriers on ARM are funky, but the kernel is here to help: -// -// * ARMv5 didn't support SMP, there is no memory barrier instruction at -// all on this architecture, or when targeting its machine code. -// -// * Some ARMv6 CPUs support SMP. A full memory barrier can be produced by -// writing a random value to a very specific coprocessor register. -// -// * On ARMv7, the "dmb" instruction is used to perform a full memory -// barrier (though writing to the co-processor will still work). -// However, on single core devices (e.g. Nexus One, or Nexus S), -// this instruction will take up to 200 ns, which is huge, even though -// it's completely un-needed on these devices. -// -// * There is no easy way to determine at runtime if the device is -// single or multi-core. However, the kernel provides a useful helper -// function at a fixed memory address (0xffff0fa0), which will always -// perform a memory barrier in the most efficient way. I.e. on single -// core devices, this is an empty function that exits immediately. -// On multi-core devices, it implements a full memory barrier. -// -// * This source could be compiled to ARMv5 machine code that runs on a -// multi-core ARMv6 or ARMv7 device. In this case, memory barriers -// are needed for correct execution. Always call the kernel helper, even -// when targeting ARMv5TE. -// - -inline void MemoryBarrier() { -#if defined(OS_LINUX) || defined(OS_ANDROID) - // Note: This is a function call, which is also an implicit compiler barrier. - typedef void (*KernelMemoryBarrierFunc)(); - ((KernelMemoryBarrierFunc)0xffff0fa0)(); -#elif defined(OS_QNX) - __cpu_membarrier(); -#else -#error MemoryBarrier() is not implemented on this platform. -#endif -} - -// An ARM toolchain would only define one of these depending on which -// variant of the target architecture is being used. This tests against -// any known ARMv6 or ARMv7 variant, where it is possible to directly -// use ldrex/strex instructions to implement fast atomic operations. -#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || \ - defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || \ - defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || \ - defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || \ - defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) - -inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev_value; - int reloop; - do { - // The following is equivalent to: - // - // prev_value = LDREX(ptr) - // reloop = 0 - // if (prev_value != old_value) - // reloop = STREX(ptr, new_value) - __asm__ __volatile__(" ldrex %0, [%3]\n" - " mov %1, #0\n" - " cmp %0, %4\n" -#ifdef __thumb2__ - " it eq\n" -#endif - " strexeq %1, %5, [%3]\n" - : "=&r"(prev_value), "=&r"(reloop), "+m"(*ptr) - : "r"(ptr), "r"(old_value), "r"(new_value) - : "cc", "memory"); - } while (reloop != 0); - return prev_value; -} - -inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 result = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - MemoryBarrier(); - return result; -} - -inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - MemoryBarrier(); - return NoBarrier_CompareAndSwap(ptr, old_value, new_value); -} - -inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - Atomic32 value; - int reloop; - do { - // Equivalent to: - // - // value = LDREX(ptr) - // value += increment - // reloop = STREX(ptr, value) - // - __asm__ __volatile__(" ldrex %0, [%3]\n" - " add %0, %0, %4\n" - " strex %1, %0, [%3]\n" - : "=&r"(value), "=&r"(reloop), "+m"(*ptr) - : "r"(ptr), "r"(increment) - : "cc", "memory"); - } while (reloop); - return value; -} - -inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - // TODO(digit): Investigate if it's possible to implement this with - // a single MemoryBarrier() operation between the LDREX and STREX. - // See http://crbug.com/246514 - MemoryBarrier(); - Atomic32 result = NoBarrier_AtomicIncrement(ptr, increment); - MemoryBarrier(); - return result; -} - -inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, - Atomic32 new_value) { - Atomic32 old_value; - int reloop; - do { - // old_value = LDREX(ptr) - // reloop = STREX(ptr, new_value) - __asm__ __volatile__(" ldrex %0, [%3]\n" - " strex %1, %4, [%3]\n" - : "=&r"(old_value), "=&r"(reloop), "+m"(*ptr) - : "r"(ptr), "r"(new_value) - : "cc", "memory"); - } while (reloop != 0); - return old_value; -} - -// This tests against any known ARMv5 variant. -#elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) || \ - defined(__ARM_ARCH_5TE__) || defined(__ARM_ARCH_5TEJ__) - -// The kernel also provides a helper function to perform an atomic -// compare-and-swap operation at the hard-wired address 0xffff0fc0. -// On ARMv5, this is implemented by a special code path that the kernel -// detects and treats specially when thread pre-emption happens. -// On ARMv6 and higher, it uses LDREX/STREX instructions instead. -// -// Note that this always perform a full memory barrier, there is no -// need to add calls MemoryBarrier() before or after it. It also -// returns 0 on success, and 1 on exit. -// -// Available and reliable since Linux 2.6.24. Both Android and ChromeOS -// use newer kernel revisions, so this should not be a concern. -namespace { - -inline int LinuxKernelCmpxchg(Atomic32 old_value, - Atomic32 new_value, - volatile Atomic32* ptr) { - typedef int (*KernelCmpxchgFunc)(Atomic32, Atomic32, volatile Atomic32*); - return ((KernelCmpxchgFunc)0xffff0fc0)(old_value, new_value, ptr); -} - -} // namespace - -inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev_value; - for (;;) { - prev_value = *ptr; - if (prev_value != old_value) - return prev_value; - if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) - return old_value; - } -} - -inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, - Atomic32 new_value) { - Atomic32 old_value; - do { - old_value = *ptr; - } while (LinuxKernelCmpxchg(old_value, new_value, ptr)); - return old_value; -} - -inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - return Barrier_AtomicIncrement(ptr, increment); -} - -inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - for (;;) { - // Atomic exchange the old value with an incremented one. - Atomic32 old_value = *ptr; - Atomic32 new_value = old_value + increment; - if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) { - // The exchange took place as expected. - return new_value; - } - // Otherwise, *ptr changed mid-loop and we need to retry. - } -} - -inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev_value; - for (;;) { - prev_value = *ptr; - if (prev_value != old_value) { - // Always ensure acquire semantics. - MemoryBarrier(); - return prev_value; - } - if (!LinuxKernelCmpxchg(old_value, new_value, ptr)) - return old_value; - } -} - -inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - // This could be implemented as: - // MemoryBarrier(); - // return NoBarrier_CompareAndSwap(); - // - // But would use 3 barriers per succesful CAS. To save performance, - // use Acquire_CompareAndSwap(). Its implementation guarantees that: - // - A succesful swap uses only 2 barriers (in the kernel helper). - // - An early return due to (prev_value != old_value) performs - // a memory barrier with no store, which is equivalent to the - // generic implementation above. - return Acquire_CompareAndSwap(ptr, old_value, new_value); -} - -#else -# error "Your CPU's ARM architecture is not supported yet" -#endif - -// NOTE: Atomicity of the following load and store operations is only -// guaranteed in case of 32-bit alignement of |ptr| values. - -inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; -} - -inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { - MemoryBarrier(); - *ptr = value; -} - -inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { return *ptr; } - -inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { - Atomic32 value = *ptr; - MemoryBarrier(); - return value; -} - -inline Atomic32 Release_Load(volatile const Atomic32* ptr) { - MemoryBarrier(); - return *ptr; -} - -} // namespace subtle -} // namespace base - -#endif // BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_
diff --git a/atomicops_internals_gcc.h b/atomicops_internals_gcc.h deleted file mode 100644 index 35c95fe..0000000 --- a/atomicops_internals_gcc.h +++ /dev/null
@@ -1,106 +0,0 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file is an internal atomic implementation, include base/atomicops.h -// instead. This file is for platforms that use GCC intrinsics rather than -// platform-specific assembly code for atomic operations. - -#ifndef BASE_ATOMICOPS_INTERNALS_GCC_H_ -#define BASE_ATOMICOPS_INTERNALS_GCC_H_ - -namespace base { -namespace subtle { - -inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev_value; - do { - if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) - return old_value; - prev_value = *ptr; - } while (prev_value == old_value); - return prev_value; -} - -inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, - Atomic32 new_value) { - Atomic32 old_value; - do { - old_value = *ptr; - } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value)); - return old_value; -} - -inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - return Barrier_AtomicIncrement(ptr, increment); -} - -inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - for (;;) { - // Atomic exchange the old value with an incremented one. - Atomic32 old_value = *ptr; - Atomic32 new_value = old_value + increment; - if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) { - // The exchange took place as expected. - return new_value; - } - // Otherwise, *ptr changed mid-loop and we need to retry. - } -} - -inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - // Since NoBarrier_CompareAndSwap uses __sync_bool_compare_and_swap, which - // is a full memory barrier, none is needed here or below in Release. - return NoBarrier_CompareAndSwap(ptr, old_value, new_value); -} - -inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - return NoBarrier_CompareAndSwap(ptr, old_value, new_value); -} - -inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; -} - -inline void MemoryBarrier() { - __sync_synchronize(); -} - -inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { - MemoryBarrier(); - *ptr = value; -} - -inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { - return *ptr; -} - -inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { - Atomic32 value = *ptr; - MemoryBarrier(); - return value; -} - -inline Atomic32 Release_Load(volatile const Atomic32* ptr) { - MemoryBarrier(); - return *ptr; -} - -} // namespace subtle -} // namespace base - -#endif // BASE_ATOMICOPS_INTERNALS_GCC_H_ -
diff --git a/atomicops_internals_mips_gcc.h b/atomicops_internals_mips_gcc.h deleted file mode 100644 index b4551b8..0000000 --- a/atomicops_internals_mips_gcc.h +++ /dev/null
@@ -1,280 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file is an internal atomic implementation, use base/atomicops.h instead. -// -// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears. - -#ifndef BASE_ATOMICOPS_INTERNALS_MIPS_GCC_H_ -#define BASE_ATOMICOPS_INTERNALS_MIPS_GCC_H_ - -namespace base { -namespace subtle { - -// Atomically execute: -// result = *ptr; -// if (*ptr == old_value) -// *ptr = new_value; -// return result; -// -// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value". -// Always return the old value of "*ptr" -// -// This routine implies no memory barriers. -inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev, tmp; - __asm__ __volatile__(".set push\n" - ".set noreorder\n" - "1:\n" - "ll %0, %5\n" // prev = *ptr - "bne %0, %3, 2f\n" // if (prev != old_value) goto 2 - "move %2, %4\n" // tmp = new_value - "sc %2, %1\n" // *ptr = tmp (with atomic check) - "beqz %2, 1b\n" // start again on atomic error - "nop\n" // delay slot nop - "2:\n" - ".set pop\n" - : "=&r" (prev), "=m" (*ptr), "=&r" (tmp) - : "r" (old_value), "r" (new_value), "m" (*ptr) - : "memory"); - return prev; -} - -// Atomically store new_value into *ptr, returning the previous value held in -// *ptr. This routine implies no memory barriers. -inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, - Atomic32 new_value) { - Atomic32 temp, old; - __asm__ __volatile__(".set push\n" - ".set noreorder\n" - "1:\n" - "ll %1, %4\n" // old = *ptr - "move %0, %3\n" // temp = new_value - "sc %0, %2\n" // *ptr = temp (with atomic check) - "beqz %0, 1b\n" // start again on atomic error - "nop\n" // delay slot nop - ".set pop\n" - : "=&r" (temp), "=&r" (old), "=m" (*ptr) - : "r" (new_value), "m" (*ptr) - : "memory"); - - return old; -} - -// Atomically increment *ptr by "increment". Returns the new value of -// *ptr with the increment applied. This routine implies no memory barriers. -inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - Atomic32 temp, temp2; - - __asm__ __volatile__(".set push\n" - ".set noreorder\n" - "1:\n" - "ll %0, %4\n" // temp = *ptr - "addu %1, %0, %3\n" // temp2 = temp + increment - "sc %1, %2\n" // *ptr = temp2 (with atomic check) - "beqz %1, 1b\n" // start again on atomic error - "addu %1, %0, %3\n" // temp2 = temp + increment - ".set pop\n" - : "=&r" (temp), "=&r" (temp2), "=m" (*ptr) - : "Ir" (increment), "m" (*ptr) - : "memory"); - // temp2 now holds the final value. - return temp2; -} - -inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - MemoryBarrier(); - Atomic32 res = NoBarrier_AtomicIncrement(ptr, increment); - MemoryBarrier(); - return res; -} - -// "Acquire" operations -// ensure that no later memory access can be reordered ahead of the operation. -// "Release" operations ensure that no previous memory access can be reordered -// after the operation. "Barrier" operations have both "Acquire" and "Release" -// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory -// access. -inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - MemoryBarrier(); - return res; -} - -inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - MemoryBarrier(); - return NoBarrier_CompareAndSwap(ptr, old_value, new_value); -} - -inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; -} - -inline void MemoryBarrier() { - __asm__ __volatile__("sync" : : : "memory"); -} - -inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { - MemoryBarrier(); - *ptr = value; -} - -inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { - return *ptr; -} - -inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { - Atomic32 value = *ptr; - MemoryBarrier(); - return value; -} - -inline Atomic32 Release_Load(volatile const Atomic32* ptr) { - MemoryBarrier(); - return *ptr; -} - -#if defined(__LP64__) -// 64-bit versions of the atomic ops. - -inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - Atomic64 prev, tmp; - __asm__ __volatile__(".set push\n" - ".set noreorder\n" - "1:\n" - "lld %0, %5\n" // prev = *ptr - "bne %0, %3, 2f\n" // if (prev != old_value) goto 2 - "move %2, %4\n" // tmp = new_value - "scd %2, %1\n" // *ptr = tmp (with atomic check) - "beqz %2, 1b\n" // start again on atomic error - "nop\n" // delay slot nop - "2:\n" - ".set pop\n" - : "=&r" (prev), "=m" (*ptr), "=&r" (tmp) - : "r" (old_value), "r" (new_value), "m" (*ptr) - : "memory"); - return prev; -} - -// Atomically store new_value into *ptr, returning the previous value held in -// *ptr. This routine implies no memory barriers. -inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, - Atomic64 new_value) { - Atomic64 temp, old; - __asm__ __volatile__(".set push\n" - ".set noreorder\n" - "1:\n" - "lld %1, %4\n" // old = *ptr - "move %0, %3\n" // temp = new_value - "scd %0, %2\n" // *ptr = temp (with atomic check) - "beqz %0, 1b\n" // start again on atomic error - "nop\n" // delay slot nop - ".set pop\n" - : "=&r" (temp), "=&r" (old), "=m" (*ptr) - : "r" (new_value), "m" (*ptr) - : "memory"); - - return old; -} - -// Atomically increment *ptr by "increment". Returns the new value of -// *ptr with the increment applied. This routine implies no memory barriers. -inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, - Atomic64 increment) { - Atomic64 temp, temp2; - - __asm__ __volatile__(".set push\n" - ".set noreorder\n" - "1:\n" - "lld %0, %4\n" // temp = *ptr - "daddu %1, %0, %3\n" // temp2 = temp + increment - "scd %1, %2\n" // *ptr = temp2 (with atomic check) - "beqz %1, 1b\n" // start again on atomic error - "daddu %1, %0, %3\n" // temp2 = temp + increment - ".set pop\n" - : "=&r" (temp), "=&r" (temp2), "=m" (*ptr) - : "Ir" (increment), "m" (*ptr) - : "memory"); - // temp2 now holds the final value. - return temp2; -} - -inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, - Atomic64 increment) { - MemoryBarrier(); - Atomic64 res = NoBarrier_AtomicIncrement(ptr, increment); - MemoryBarrier(); - return res; -} - -// "Acquire" operations -// ensure that no later memory access can be reordered ahead of the operation. -// "Release" operations ensure that no previous memory access can be reordered -// after the operation. "Barrier" operations have both "Acquire" and "Release" -// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory -// access. -inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - Atomic64 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - MemoryBarrier(); - return res; -} - -inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - MemoryBarrier(); - return NoBarrier_CompareAndSwap(ptr, old_value, new_value); -} - -inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { - *ptr = value; -} - -inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { - MemoryBarrier(); - *ptr = value; -} - -inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { - return *ptr; -} - -inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { - Atomic64 value = *ptr; - MemoryBarrier(); - return value; -} - -inline Atomic64 Release_Load(volatile const Atomic64* ptr) { - MemoryBarrier(); - return *ptr; -} -#endif - -} // namespace subtle -} // namespace base - -#endif // BASE_ATOMICOPS_INTERNALS_MIPS_GCC_H_
diff --git a/atomicops_internals_x86_gcc.cc b/atomicops_internals_x86_gcc.cc deleted file mode 100644 index c21e96d..0000000 --- a/atomicops_internals_x86_gcc.cc +++ /dev/null
@@ -1,103 +0,0 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This module gets enough CPU information to optimize the -// atomicops module on x86. - -#include <stdint.h> -#include <string.h> - -#include "base/atomicops.h" - -// Inline cpuid instruction. In PIC compilations, %ebx contains the address -// of the global offset table. To avoid breaking such executables, this code -// must preserve that register's value across cpuid instructions. -// -// The include guards are the same as in atomicops.h. -#if defined(__i386__) -#define cpuid(a, b, c, d, inp) \ - asm("mov %%ebx, %%edi\n" \ - "cpuid\n" \ - "xchg %%edi, %%ebx\n" \ - : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) -#elif defined(__x86_64__) -#define cpuid(a, b, c, d, inp) \ - asm("mov %%rbx, %%rdi\n" \ - "cpuid\n" \ - "xchg %%rdi, %%rbx\n" \ - : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) -#endif - -#if defined(cpuid) // initialize the struct only on x86 - -// Set the flags so that code will run correctly and conservatively, so even -// if we haven't been initialized yet, we're probably single threaded, and our -// default values should hopefully be pretty safe. -struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures = { - false, // bug can't exist before process spawns multiple threads - false, // Chrome requires SSE2, but for transition assume not and initialize - // this properly. - false, // cmpxchg16b isn't present on early AMD64 CPUs. -}; - -namespace { - -// Initialize the AtomicOps_Internalx86CPUFeatures struct. -void AtomicOps_Internalx86CPUFeaturesInit() { - uint32_t eax; - uint32_t ebx; - uint32_t ecx; - uint32_t edx; - - // Get vendor string (issue CPUID with eax = 0) - cpuid(eax, ebx, ecx, edx, 0); - char vendor[13]; - memcpy(vendor, &ebx, 4); - memcpy(vendor + 4, &edx, 4); - memcpy(vendor + 8, &ecx, 4); - vendor[12] = 0; - - // get feature flags in ecx/edx, and family/model in eax - cpuid(eax, ebx, ecx, edx, 1); - - int family = (eax >> 8) & 0xf; // family and model fields - int model = (eax >> 4) & 0xf; - if (family == 0xf) { // use extended family and model fields - family += (eax >> 20) & 0xff; - model += ((eax >> 16) & 0xf) << 4; - } - - // Opteron Rev E has a bug in which on very rare occasions a locked - // instruction doesn't act as a read-acquire barrier if followed by a - // non-locked read-modify-write instruction. Rev F has this bug in - // pre-release versions, but not in versions released to customers, - // so we test only for Rev E, which is family 15, model 32..63 inclusive. - if (strcmp(vendor, "AuthenticAMD") == 0 && // AMD - family == 15 && - 32 <= model && model <= 63) { - AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = true; - } else { - AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = false; - } - - // edx bit 26 is SSE2 which we use to tell use whether we can use mfence - AtomicOps_Internalx86CPUFeatures.has_sse2 = ((edx >> 26) & 1); - - // ecx bit 13 indicates whether the cmpxchg16b instruction is supported - AtomicOps_Internalx86CPUFeatures.has_cmpxchg16b = ((ecx >> 13) & 1); -} - -class AtomicOpsx86Initializer { - public: - AtomicOpsx86Initializer() { - AtomicOps_Internalx86CPUFeaturesInit(); - } -}; - -// A global to get use initialized on startup via static initialization :/ -AtomicOpsx86Initializer g_initer; - -} // namespace - -#endif // if x86
diff --git a/atomicops_internals_x86_gcc.h b/atomicops_internals_x86_gcc.h deleted file mode 100644 index f0d2242..0000000 --- a/atomicops_internals_x86_gcc.h +++ /dev/null
@@ -1,228 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file is an internal atomic implementation, use base/atomicops.h instead. - -#ifndef BASE_ATOMICOPS_INTERNALS_X86_GCC_H_ -#define BASE_ATOMICOPS_INTERNALS_X86_GCC_H_ - -#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory") - -namespace base { -namespace subtle { - -// 32-bit low-level operations on any platform. - -inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 prev; - __asm__ __volatile__("lock; cmpxchgl %1,%2" - : "=a" (prev) - : "q" (new_value), "m" (*ptr), "0" (old_value) - : "memory"); - return prev; -} - -inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, - Atomic32 new_value) { - __asm__ __volatile__("xchgl %1,%0" // The lock prefix is implicit for xchg. - : "=r" (new_value) - : "m" (*ptr), "0" (new_value) - : "memory"); - return new_value; // Now it's the previous value. -} - -inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - Atomic32 temp = increment; - __asm__ __volatile__("lock; xaddl %0,%1" - : "+r" (temp), "+m" (*ptr) - : : "memory"); - // temp now holds the old value of *ptr - return temp + increment; -} - -inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, - Atomic32 increment) { - Atomic32 temp = increment; - __asm__ __volatile__("lock; xaddl %0,%1" - : "+r" (temp), "+m" (*ptr) - : : "memory"); - // temp now holds the old value of *ptr - if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { - __asm__ __volatile__("lfence" : : : "memory"); - } - return temp + increment; -} - -inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - Atomic32 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { - __asm__ __volatile__("lfence" : : : "memory"); - } - return x; -} - -inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value) { - return NoBarrier_CompareAndSwap(ptr, old_value, new_value); -} - -inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; -} - -inline void MemoryBarrier() { - __asm__ __volatile__("mfence" : : : "memory"); -} - -inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { - ATOMICOPS_COMPILER_BARRIER(); - *ptr = value; // An x86 store acts as a release barrier. - // See comments in Atomic64 version of Release_Store(), below. -} - -inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { - return *ptr; -} - -inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { - Atomic32 value = *ptr; // An x86 load acts as a acquire barrier. - // See comments in Atomic64 version of Release_Store(), below. - ATOMICOPS_COMPILER_BARRIER(); - return value; -} - -inline Atomic32 Release_Load(volatile const Atomic32* ptr) { - MemoryBarrier(); - return *ptr; -} - -#if defined(__x86_64__) - -// 64-bit low-level operations on 64-bit platform. - -inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - Atomic64 prev; - __asm__ __volatile__("lock; cmpxchgq %1,%2" - : "=a" (prev) - : "q" (new_value), "m" (*ptr), "0" (old_value) - : "memory"); - return prev; -} - -inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, - Atomic64 new_value) { - __asm__ __volatile__("xchgq %1,%0" // The lock prefix is implicit for xchg. - : "=r" (new_value) - : "m" (*ptr), "0" (new_value) - : "memory"); - return new_value; // Now it's the previous value. -} - -inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, - Atomic64 increment) { - Atomic64 temp = increment; - __asm__ __volatile__("lock; xaddq %0,%1" - : "+r" (temp), "+m" (*ptr) - : : "memory"); - // temp now contains the previous value of *ptr - return temp + increment; -} - -inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, - Atomic64 increment) { - Atomic64 temp = increment; - __asm__ __volatile__("lock; xaddq %0,%1" - : "+r" (temp), "+m" (*ptr) - : : "memory"); - // temp now contains the previous value of *ptr - if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { - __asm__ __volatile__("lfence" : : : "memory"); - } - return temp + increment; -} - -inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { - *ptr = value; -} - -inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { - *ptr = value; - MemoryBarrier(); -} - -inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { - ATOMICOPS_COMPILER_BARRIER(); - - *ptr = value; // An x86 store acts as a release barrier - // for current AMD/Intel chips as of Jan 2008. - // See also Acquire_Load(), below. - - // When new chips come out, check: - // IA-32 Intel Architecture Software Developer's Manual, Volume 3: - // System Programming Guide, Chatper 7: Multiple-processor management, - // Section 7.2, Memory Ordering. - // Last seen at: - // http://developer.intel.com/design/pentium4/manuals/index_new.htm - // - // x86 stores/loads fail to act as barriers for a few instructions (clflush - // maskmovdqu maskmovq movntdq movnti movntpd movntps movntq) but these are - // not generated by the compiler, and are rare. Users of these instructions - // need to know about cache behaviour in any case since all of these involve - // either flushing cache lines or non-temporal cache hints. -} - -inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { - return *ptr; -} - -inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { - Atomic64 value = *ptr; // An x86 load acts as a acquire barrier, - // for current AMD/Intel chips as of Jan 2008. - // See also Release_Store(), above. - ATOMICOPS_COMPILER_BARRIER(); - return value; -} - -inline Atomic64 Release_Load(volatile const Atomic64* ptr) { - MemoryBarrier(); - return *ptr; -} - -inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - Atomic64 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); - if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { - __asm__ __volatile__("lfence" : : : "memory"); - } - return x; -} - -inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value) { - return NoBarrier_CompareAndSwap(ptr, old_value, new_value); -} - -#endif // defined(__x86_64__) - -} // namespace subtle -} // namespace base - -#undef ATOMICOPS_COMPILER_BARRIER - -#endif // BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
diff --git a/base.isolate b/base.isolate index c7ba651..948c600 100644 --- a/base.isolate +++ b/base.isolate
@@ -36,7 +36,9 @@ ['OS=="win" and asan==1 and component=="shared_library"', { 'variables': { 'files': [ - '../third_party/llvm-build/Release+Asserts/lib/clang/3.7.0/lib/windows/clang_rt.asan_dynamic-i386.dll', + # We only need x.y.z/lib/windows/clang_rt.asan_dynamic-i386.dll, + # but since the version (x.y.z) changes, just grab the whole dir. + '../third_party/llvm-build/Release+Asserts/lib/clang/', ], }, }],
diff --git a/base_paths_mac.mm b/base_paths_mac.mm index 9864eb3..a9d01f2 100644 --- a/base_paths_mac.mm +++ b/base_paths_mac.mm
@@ -29,8 +29,8 @@ _NSGetExecutablePath(NULL, &executable_length); DCHECK_GT(executable_length, 1u); std::string executable_path; - int rv = _NSGetExecutablePath(WriteInto(&executable_path, executable_length), - &executable_length); + int rv = _NSGetExecutablePath( + base::WriteInto(&executable_path, executable_length), &executable_length); DCHECK_EQ(rv, 0); // _NSGetExecutablePath may return paths containing ./ or ../ which makes
diff --git a/base_paths_win.cc b/base_paths_win.cc index 4ecb59d..58f925f 100644 --- a/base_paths_win.cc +++ b/base_paths_win.cc
@@ -32,14 +32,16 @@ FilePath cur; switch (key) { case base::FILE_EXE: - GetModuleFileName(NULL, system_buffer, MAX_PATH); + if (GetModuleFileName(NULL, system_buffer, MAX_PATH) == 0) + return false; cur = FilePath(system_buffer); break; case base::FILE_MODULE: { // the resource containing module is assumed to be the one that // this code lives in, whether that's a dll or exe HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase); - GetModuleFileName(this_module, system_buffer, MAX_PATH); + if (GetModuleFileName(this_module, system_buffer, MAX_PATH) == 0) + return false; cur = FilePath(system_buffer); break; }
diff --git a/base_switches.cc b/base_switches.cc index 3076540..6d517e5 100644 --- a/base_switches.cc +++ b/base_switches.cc
@@ -67,6 +67,11 @@ // chrome://profiler. const char kProfilerTimingDisabledValue[] = "0"; +#if defined(OS_WIN) +// Disables the USB keyboard detection for blocking the OSK on Win8+. +const char kDisableUsbKeyboardDetect[] = "disable-usb-keyboard-detect"; +#endif + #if defined(OS_POSIX) // Used for turning on Breakpad crash reporting in a debug environment where // crash reporting is typically compiled but disabled.
diff --git a/base_switches.h b/base_switches.h index c579f6a..bbd590b 100644 --- a/base_switches.h +++ b/base_switches.h
@@ -27,6 +27,10 @@ extern const char kVModule[]; extern const char kWaitForDebugger[]; +#if defined(OS_WIN) +extern const char kDisableUsbKeyboardDetect[]; +#endif + #if defined(OS_POSIX) extern const char kEnableCrashReporterForTesting[]; #endif
diff --git a/basictypes.h b/basictypes.h index bf75e67..d71abd9 100644 --- a/basictypes.h +++ b/basictypes.h
@@ -16,7 +16,7 @@ #include <stdint.h> // For intptr_t. #include "base/macros.h" -#include "base/port.h" // Types that only need exist on certain systems. +#include "build/build_config.h" // DEPRECATED: Please use (u)int{8,16,32,64}_t instead (and include <stdint.h>). typedef int8_t int8;
diff --git a/compiler_specific.h b/compiler_specific.h index 63297dc..66dc80d 100644 --- a/compiler_specific.h +++ b/compiler_specific.h
@@ -140,7 +140,7 @@ // Annotate a function indicating the caller must examine the return value. // Use like: // int foo() WARN_UNUSED_RESULT; -// To explicitly ignore a result, see |ignore_result()| in <base/basictypes.h>. +// To explicitly ignore a result, see |ignore_result()| in base/macros.h. #if defined(COMPILER_GCC) #define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) #else
diff --git a/containers/scoped_ptr_map.h b/containers/scoped_ptr_map.h new file mode 100644 index 0000000..a4605e3 --- /dev/null +++ b/containers/scoped_ptr_map.h
@@ -0,0 +1,144 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_CONTAINERS_SCOPED_PTR_MAP_H_ +#define BASE_CONTAINERS_SCOPED_PTR_MAP_H_ + +#include <functional> +#include <map> +#include <utility> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/move.h" +#include "base/stl_util.h" + +namespace base { + +// ScopedPtrMap provides a std::map that supports scoped_ptr values. It ensures +// that the map's values are properly deleted when removed from the map, or when +// the map is destroyed. +// +// |ScopedPtr| must be a type scoped_ptr<T>. This is for compatibility with +// std::map in C++11. +template <class Key, class ScopedPtr, class Compare = std::less<Key>> +class ScopedPtrMap { + MOVE_ONLY_TYPE_WITH_MOVE_CONSTRUCTOR_FOR_CPP_03(ScopedPtrMap) + + using Container = std::map<Key, typename ScopedPtr::element_type*, Compare>; + + public: + using allocator_type = typename Container::allocator_type; + using size_type = typename Container::size_type; + using difference_type = typename Container::difference_type; + using reference = typename Container::reference; + using const_reference = typename Container::const_reference; + using key_type = typename Container::key_type; + using mapped_type = ScopedPtr; + using key_compare = typename Container::key_compare; + using const_iterator = typename Container::const_iterator; + using const_reverse_iterator = typename Container::const_reverse_iterator; + + ScopedPtrMap() {} + ~ScopedPtrMap() { clear(); } + ScopedPtrMap(ScopedPtrMap<Key, ScopedPtr>&& other) { swap(other); } + + ScopedPtrMap& operator=(ScopedPtrMap<Key, ScopedPtr>&& rhs) { + swap(rhs); + return *this; + } + + const_iterator find(const Key& k) const { return data_.find(k); } + size_type count(const Key& k) const { return data_.count(k); } + + bool empty() const { return data_.empty(); } + size_t size() const { return data_.size(); } + + const_reverse_iterator rbegin() const { return data_.rbegin(); } + const_reverse_iterator rend() const { return data_.rend(); } + + const_iterator begin() const { return data_.begin(); } + const_iterator end() const { return data_.end(); } + + void swap(ScopedPtrMap<Key, ScopedPtr>& other) { data_.swap(other.data_); } + + void clear() { STLDeleteValues(&data_); } + + // Inserts |val| into the map, associated with |key|. + std::pair<const_iterator, bool> insert(const Key& key, ScopedPtr val) { + auto result = data_.insert(std::make_pair(key, val.get())); + if (result.second) + ignore_result(val.release()); + return result; + } + + // Inserts |val| into the map, associated with |key|. Overwrites any existing + // element at |key|. + void set(const Key& key, ScopedPtr val) { + typename ScopedPtr::element_type*& val_ref = data_[key]; + delete val_ref; + val_ref = val.release(); + } + + void erase(const_iterator position) { + DCHECK(position != end()); + delete position->second; + // Key-based lookup (cannot use const_iterator overload in C++03 library). + data_.erase(position->first); + } + + size_type erase(const Key& k) { + typename Container::iterator it = data_.find(k); + if (it == end()) + return 0; + + delete it->second; + data_.erase(it); + return 1; + } + + void erase(const_iterator first, const_iterator last) { + STLDeleteContainerPairSecondPointers(first, last); + // Need non-const iterators as required by the C++03 library. + data_.erase(ConstIteratorToIterator(first), ConstIteratorToIterator(last)); + } + + // Like |erase()|, but returns the element instead of deleting it. + ScopedPtr take_and_erase(const_iterator position) { + DCHECK(position != end()); + if (position == end()) + return ScopedPtr(); + + ScopedPtr ret(position->second); + // Key-based lookup (cannot use const_iterator overload in C++03 library). + data_.erase(position->first); + return ret.Pass(); + } + + // Like |erase()|, but returns the element instead of deleting it. + ScopedPtr take_and_erase(const Key& k) { + typename Container::iterator it = data_.find(k); + if (it == end()) + return ScopedPtr(); + + ScopedPtr ret(it->second); + data_.erase(it); + return ret.Pass(); + } + + private: + Container data_; + + typename Container::iterator ConstIteratorToIterator(const_iterator it) { + // This is the only way to convert a const iterator to a non-const iterator + // in C++03 (get the key and do the lookup again). + if (it == data_.end()) + return data_.end(); + return data_.find(it->first); + }; +}; + +} // namespace base + +#endif // BASE_CONTAINERS_SCOPED_PTR_MAP_H_
diff --git a/containers/scoped_ptr_map_unittest.cc b/containers/scoped_ptr_map_unittest.cc new file mode 100644 index 0000000..46843b3 --- /dev/null +++ b/containers/scoped_ptr_map_unittest.cc
@@ -0,0 +1,240 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/containers/scoped_ptr_map.h" + +#include <functional> +#include <map> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +// A ScopedDestroyer sets a Boolean to true upon destruction. +class ScopedDestroyer { + public: + ScopedDestroyer(bool* destroyed) : destroyed_(destroyed) { + *destroyed_ = false; + } + + ~ScopedDestroyer() { *destroyed_ = true; } + + private: + bool* destroyed_; +}; + +TEST(ScopedPtrMapTest, Insert) { + bool destroyed1 = false; + bool destroyed2 = false; + { + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + + // Insert to new key. + ScopedDestroyer* elem1 = new ScopedDestroyer(&destroyed1); + EXPECT_FALSE(destroyed1); + EXPECT_TRUE(scoped_map.insert(0, make_scoped_ptr(elem1)).second); + EXPECT_EQ(elem1, scoped_map.find(0)->second); + EXPECT_FALSE(destroyed1); + + // Insert to existing key. + ScopedDestroyer* elem2 = new ScopedDestroyer(&destroyed2); + EXPECT_FALSE(destroyed2); + EXPECT_FALSE(scoped_map.insert(0, make_scoped_ptr(elem2)).second); + EXPECT_EQ(elem1, scoped_map.find(0)->second); + + EXPECT_FALSE(destroyed1); + EXPECT_TRUE(destroyed2); + } + EXPECT_TRUE(destroyed1); +} + +TEST(ScopedPtrMapTest, Set) { + bool destroyed1 = false; + bool destroyed2 = false; + { + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + + // Set a new key. + ScopedDestroyer* elem1 = new ScopedDestroyer(&destroyed1); + EXPECT_FALSE(destroyed1); + scoped_map.set(0, make_scoped_ptr(elem1)); + EXPECT_EQ(elem1, scoped_map.find(0)->second); + EXPECT_FALSE(destroyed1); + + // Set to replace an existing key. + ScopedDestroyer* elem2 = new ScopedDestroyer(&destroyed2); + EXPECT_FALSE(destroyed2); + scoped_map.set(0, make_scoped_ptr(elem2)); + EXPECT_EQ(elem2, scoped_map.find(0)->second); + + EXPECT_TRUE(destroyed1); + EXPECT_FALSE(destroyed2); + } + EXPECT_TRUE(destroyed1); + EXPECT_TRUE(destroyed2); +} + +TEST(ScopedPtrMapTest, EraseIterator) { + bool destroyed = false; + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + scoped_map.insert(0, make_scoped_ptr(new ScopedDestroyer(&destroyed))); + EXPECT_FALSE(destroyed); + scoped_map.erase(scoped_map.find(0)); + EXPECT_TRUE(destroyed); + EXPECT_TRUE(scoped_map.empty()); +} + +TEST(ScopedPtrMapTest, EraseKey) { + bool destroyed = false; + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + scoped_map.insert(0, make_scoped_ptr(new ScopedDestroyer(&destroyed))); + EXPECT_FALSE(destroyed); + EXPECT_EQ(1u, scoped_map.erase(0)); + EXPECT_TRUE(destroyed); + EXPECT_TRUE(scoped_map.empty()); + + // Test erase of a non-existent key. + EXPECT_EQ(0u, scoped_map.erase(7)); +} + +TEST(ScopedPtrMapTest, EraseRange) { + bool destroyed1 = false; + bool destroyed2 = false; + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + + scoped_map.insert(0, make_scoped_ptr(new ScopedDestroyer(&destroyed1))); + EXPECT_FALSE(destroyed1); + + scoped_map.insert(1, make_scoped_ptr(new ScopedDestroyer(&destroyed2))); + EXPECT_FALSE(destroyed2); + + scoped_map.erase(scoped_map.find(0), scoped_map.end()); + EXPECT_TRUE(destroyed1); + EXPECT_TRUE(destroyed2); + EXPECT_TRUE(scoped_map.empty()); +} + +TEST(ScopedPtrMapTest, TakeAndErase) { + bool destroyed = false; + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + ScopedDestroyer* elem = new ScopedDestroyer(&destroyed); + scoped_map.insert(0, make_scoped_ptr(elem)); + EXPECT_EQ(elem, scoped_map.find(0)->second); + EXPECT_FALSE(destroyed); + scoped_ptr<ScopedDestroyer> object = scoped_map.take_and_erase(0); + EXPECT_EQ(elem, object.get()); + EXPECT_FALSE(destroyed); + EXPECT_TRUE(scoped_map.empty()); + object.reset(); + EXPECT_TRUE(destroyed); +} + +TEST(ScopedPtrMapTest, Clear) { + bool destroyed = false; + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + scoped_map.insert(0, make_scoped_ptr(new ScopedDestroyer(&destroyed))); + EXPECT_FALSE(destroyed); + scoped_map.clear(); + EXPECT_TRUE(destroyed); + EXPECT_TRUE(scoped_map.empty()); +} + +TEST(ScopedPtrMapTest, Compare) { + // Construct a ScopedPtrMap with a custom comparison function. + bool destroyed = false; + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>, std::greater<int>> scoped_map; + scoped_map.insert(0, make_scoped_ptr(new ScopedDestroyer(&destroyed))); + scoped_map.insert(1, make_scoped_ptr(new ScopedDestroyer(&destroyed))); + + auto it = scoped_map.begin(); + EXPECT_EQ(1, it->first); + ++it; + EXPECT_EQ(0, it->first); +} + +TEST(ScopedPtrMapTest, Scope) { + bool destroyed = false; + { + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + scoped_map.insert(0, make_scoped_ptr(new ScopedDestroyer(&destroyed))); + EXPECT_FALSE(destroyed); + } + EXPECT_TRUE(destroyed); +} + +TEST(ScopedPtrMapTest, MoveConstruct) { + bool destroyed = false; + { + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + ScopedDestroyer* elem = new ScopedDestroyer(&destroyed); + scoped_map.insert(0, make_scoped_ptr(elem)); + EXPECT_EQ(elem, scoped_map.find(0)->second); + EXPECT_FALSE(destroyed); + EXPECT_FALSE(scoped_map.empty()); + + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map_copy( + scoped_map.Pass()); + EXPECT_TRUE(scoped_map.empty()); + EXPECT_FALSE(scoped_map_copy.empty()); + EXPECT_EQ(elem, scoped_map_copy.find(0)->second); + EXPECT_FALSE(destroyed); + } + EXPECT_TRUE(destroyed); +} + +TEST(ScopedPtrMapTest, MoveAssign) { + bool destroyed = false; + { + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + ScopedDestroyer* elem = new ScopedDestroyer(&destroyed); + scoped_map.insert(0, make_scoped_ptr(elem)); + EXPECT_EQ(elem, scoped_map.find(0)->second); + EXPECT_FALSE(destroyed); + EXPECT_FALSE(scoped_map.empty()); + + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map_assign; + scoped_map_assign = scoped_map.Pass(); + EXPECT_TRUE(scoped_map.empty()); + EXPECT_FALSE(scoped_map_assign.empty()); + EXPECT_EQ(elem, scoped_map_assign.find(0)->second); + EXPECT_FALSE(destroyed); + } + EXPECT_TRUE(destroyed); +} + +template <typename Key, typename ScopedPtr> +ScopedPtrMap<Key, ScopedPtr> PassThru(ScopedPtrMap<Key, ScopedPtr> scoper) { + return scoper; +} + +TEST(ScopedPtrMapTest, Passed) { + bool destroyed = false; + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> scoped_map; + ScopedDestroyer* elem = new ScopedDestroyer(&destroyed); + scoped_map.insert(0, make_scoped_ptr(elem)); + EXPECT_EQ(elem, scoped_map.find(0)->second); + EXPECT_FALSE(destroyed); + + base::Callback<ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>>(void)> + callback = base::Bind(&PassThru<int, scoped_ptr<ScopedDestroyer>>, + base::Passed(&scoped_map)); + EXPECT_TRUE(scoped_map.empty()); + EXPECT_FALSE(destroyed); + + ScopedPtrMap<int, scoped_ptr<ScopedDestroyer>> result = callback.Run(); + EXPECT_TRUE(scoped_map.empty()); + EXPECT_EQ(elem, result.find(0)->second); + EXPECT_FALSE(destroyed); + + result.clear(); + EXPECT_TRUE(destroyed); +}; + +} // namespace +} // namespace base
diff --git a/debug/crash_logging.cc b/debug/crash_logging.cc index f9b4449..058b476 100644 --- a/debug/crash_logging.cc +++ b/debug/crash_logging.cc
@@ -119,7 +119,7 @@ hex_backtrace.push_back(s); } - value = JoinString(hex_backtrace, ' '); + value = base::JoinString(hex_backtrace, " "); // Warn if this exceeds the breakpad limits. DCHECK_LE(value.length(), kBreakpadValueMax);
diff --git a/debug/profiler.cc b/debug/profiler.cc index ed553cd..924c769 100644 --- a/debug/profiler.cc +++ b/debug/profiler.cc
@@ -7,8 +7,8 @@ #include <string> #include "base/process/process_handle.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" #if defined(OS_WIN) #include "base/win/pe_image.h" @@ -30,8 +30,8 @@ void StartProfiling(const std::string& name) { ++profile_count; std::string full_name(name); - std::string pid = StringPrintf("%d", GetCurrentProcId()); - std::string count = StringPrintf("%d", profile_count); + std::string pid = IntToString(GetCurrentProcId()); + std::string count = IntToString(profile_count); ReplaceSubstringsAfterOffset(&full_name, 0, "{pid}", pid); ReplaceSubstringsAfterOffset(&full_name, 0, "{count}", count); ProfilerStart(full_name.c_str());
diff --git a/debug/stack_trace_posix.cc b/debug/stack_trace_posix.cc index 76cb524..8749bed 100644 --- a/debug/stack_trace_posix.cc +++ b/debug/stack_trace_posix.cc
@@ -788,7 +788,8 @@ // Handle negative numbers (only for base 10). if (i < 0 && base == 10) { - j = -i; + // This does "j = -i" while avoiding integer overflow. + j = static_cast<uintptr_t>(-(i + 1)) + 1; // Make sure we can write the '-' character. if (++n > sz) {
diff --git a/debug/task_annotator.cc b/debug/task_annotator.cc index 19df8cb..b74d390 100644 --- a/debug/task_annotator.cc +++ b/debug/task_annotator.cc
@@ -26,7 +26,6 @@ } void TaskAnnotator::RunTask(const char* queue_function, - const char* run_function, const PendingTask& pending_task) { tracked_objects::TaskStopwatch stopwatch; stopwatch.Start(); @@ -39,18 +38,6 @@ "queue_duration", queue_duration.InMilliseconds()); - // When tracing memory for posted tasks it's more valuable to attribute the - // memory allocations to the source function than generically to the task - // runner. - TRACE_EVENT_WITH_MEMORY_TAG2( - "toplevel", - run_function, - pending_task.posted_from.function_name(), // Name for memory tracking. - "src_file", - pending_task.posted_from.file_name(), - "src_func", - pending_task.posted_from.function_name()); - // Before running the task, store the program counter where it was posted // and deliberately alias it to ensure it is on the stack if the task // crashes. Be careful not to assume that the variable itself will have the
diff --git a/debug/task_annotator.h b/debug/task_annotator.h index aa5f17b..74068d9 100644 --- a/debug/task_annotator.h +++ b/debug/task_annotator.h
@@ -25,11 +25,8 @@ const PendingTask& pending_task); // Run a previously queued task. |queue_function| should match what was - // passed into |DidQueueTask| for this task. |run_function| is used as the - // name for the trace event that surrounds the task's execution. - void RunTask(const char* queue_function, - const char* run_function, - const PendingTask& pending_task); + // passed into |DidQueueTask| for this task. + void RunTask(const char* queue_function, const PendingTask& pending_task); private: // Creates a process-wide unique ID to represent this task in trace events. @@ -40,6 +37,13 @@ DISALLOW_COPY_AND_ASSIGN(TaskAnnotator); }; +#define TRACE_TASK_EXECUTION(run_function, task) \ + TRACE_EVENT_WITH_MEMORY_TAG2( \ + "toplevel", (run_function), \ + (task).posted_from.function_name(), /* Name for memory tracking. */ \ + "src_file", (task).posted_from.file_name(), "src_func", \ + (task).posted_from.function_name()); + } // namespace debug } // namespace base
diff --git a/debug/task_annotator_unittest.cc b/debug/task_annotator_unittest.cc index ddffc21..9f5c442 100644 --- a/debug/task_annotator_unittest.cc +++ b/debug/task_annotator_unittest.cc
@@ -24,8 +24,7 @@ TaskAnnotator annotator; annotator.DidQueueTask("TaskAnnotatorTest::Queue", pending_task); EXPECT_EQ(0, result); - annotator.RunTask( - "TaskAnnotatorTest::Queue", "TaskAnnotatorTest::Run", pending_task); + annotator.RunTask("TaskAnnotatorTest::Queue", pending_task); EXPECT_EQ(123, result); }
diff --git a/file_version_info_unittest.cc b/file_version_info_unittest.cc index 9b10d04..c5e9859 100644 --- a/file_version_info_unittest.cc +++ b/file_version_info_unittest.cc
@@ -32,53 +32,49 @@ #if defined(OS_WIN) TEST(FileVersionInfoTest, HardCodedProperties) { - const wchar_t* kDLLNames[] = { - L"FileVersionInfoTest1.dll" - }; + const wchar_t kDLLName[] = {L"FileVersionInfoTest1.dll"}; - const wchar_t* kExpectedValues[1][15] = { + const wchar_t* const kExpectedValues[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"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 + L"This is the original filename", // original_filename + L"This is my file description", // file_description + L"1.2.3.4", // file_version + L"This is the legal copyright", // legal_copyright + L"This is the legal trademarks", // legal_trademarks + L"This is the last change", // last_change }; - for (int i = 0; i < arraysize(kDLLNames); ++i) { - FilePath dll_path = GetTestDataPath(); - dll_path = dll_path.Append(kDLLNames[i]); + FilePath dll_path = GetTestDataPath(); + dll_path = dll_path.Append(kDLLName); - scoped_ptr<FileVersionInfo> version_info( - FileVersionInfo::CreateFileVersionInfo(dll_path)); + 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()); - } + int j = 0; + EXPECT_EQ(kExpectedValues[j++], version_info->company_name()); + EXPECT_EQ(kExpectedValues[j++], version_info->company_short_name()); + EXPECT_EQ(kExpectedValues[j++], version_info->product_name()); + EXPECT_EQ(kExpectedValues[j++], version_info->product_short_name()); + EXPECT_EQ(kExpectedValues[j++], version_info->internal_name()); + EXPECT_EQ(kExpectedValues[j++], version_info->product_version()); + EXPECT_EQ(kExpectedValues[j++], version_info->private_build()); + EXPECT_EQ(kExpectedValues[j++], version_info->special_build()); + EXPECT_EQ(kExpectedValues[j++], version_info->comments()); + EXPECT_EQ(kExpectedValues[j++], version_info->original_filename()); + EXPECT_EQ(kExpectedValues[j++], version_info->file_description()); + EXPECT_EQ(kExpectedValues[j++], version_info->file_version()); + EXPECT_EQ(kExpectedValues[j++], version_info->legal_copyright()); + EXPECT_EQ(kExpectedValues[j++], version_info->legal_trademarks()); + EXPECT_EQ(kExpectedValues[j++], version_info->last_change()); } #endif
diff --git a/files/file.cc b/files/file.cc index 58f80c5..9a4ddb1 100644 --- a/files/file.cc +++ b/files/file.cc
@@ -52,22 +52,30 @@ File::File(RValue other) : file_(other.object->TakePlatformFile()), - path_(other.object->path_), + tracing_path_(other.object->tracing_path_), error_details_(other.object->error_details()), created_(other.object->created()), - async_(other.object->async_) { -} + async_(other.object->async_) {} File::~File() { // Go through the AssertIOAllowed logic. Close(); } +// static +File File::CreateForAsyncHandle(PlatformFile platform_file) { + File file(platform_file); + // It would be nice if we could validate that |platform_file| was opened with + // FILE_FLAG_OVERLAPPED on Windows but this doesn't appear to be possible. + file.async_ = true; + return file.Pass(); +} + File& File::operator=(RValue other) { if (this != other.object) { Close(); SetPlatformFile(other.object->TakePlatformFile()); - path_ = other.object->path_; + tracing_path_ = other.object->tracing_path_; error_details_ = other.object->error_details(); created_ = other.object->created(); async_ = other.object->async_; @@ -81,9 +89,10 @@ error_details_ = FILE_ERROR_ACCESS_DENIED; return; } - path_ = path; + if (FileTracing::IsCategoryEnabled()) + tracing_path_ = path; SCOPED_FILE_TRACE("Initialize"); - DoInitialize(flags); + DoInitialize(path, flags); } #endif
diff --git a/files/file.h b/files/file.h index b21b159..cba4353 100644 --- a/files/file.h +++ b/files/file.h
@@ -176,6 +176,9 @@ ~File(); + // Takes ownership of |platform_file|. + static File CreateForAsyncHandle(PlatformFile platform_file); + // Move operator= for C++03 move emulation of this type. File& operator=(RValue other); @@ -352,9 +355,9 @@ }; #endif - // Creates or opens the given file. Only called if |path_| has no + // Creates or opens the given file. Only called if |path| has no // traversal ('..') components. - void DoInitialize(uint32 flags); + void DoInitialize(const FilePath& path, uint32 flags); // TODO(tnagel): Reintegrate into Flush() once histogram isn't needed anymore, // cf. issue 473337. @@ -368,8 +371,9 @@ MemoryCheckingScopedFD file_; #endif - // Path that |Initialize()| was called with. Only set if safe (i.e. no '..'). - FilePath path_; + // A path to use for tracing purposes. Set if file tracing is enabled during + // |Initialize()|. + FilePath tracing_path_; // Object tied to the lifetime of |this| that enables/disables tracing. FileTracing::ScopedEnabler trace_enabler_;
diff --git a/files/file_enumerator_win.cc b/files/file_enumerator_win.cc index 931d154..ae41a46 100644 --- a/files/file_enumerator_win.cc +++ b/files/file_enumerator_win.cc
@@ -43,10 +43,10 @@ FileEnumerator::FileEnumerator(const FilePath& root_path, bool recursive, int file_type) - : recursive_(recursive), - file_type_(file_type), - has_find_data_(false), - find_handle_(INVALID_HANDLE_VALUE) { + : has_find_data_(false), + find_handle_(INVALID_HANDLE_VALUE), + recursive_(recursive), + file_type_(file_type) { // INCLUDE_DOT_DOT must not be specified if recursive. DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); memset(&find_data_, 0, sizeof(find_data_)); @@ -57,11 +57,11 @@ bool recursive, int file_type, const FilePath::StringType& pattern) - : recursive_(recursive), + : has_find_data_(false), + find_handle_(INVALID_HANDLE_VALUE), + recursive_(recursive), file_type_(file_type), - has_find_data_(false), - pattern_(pattern), - find_handle_(INVALID_HANDLE_VALUE) { + pattern_(pattern) { // INCLUDE_DOT_DOT must not be specified if recursive. DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); memset(&find_data_, 0, sizeof(find_data_));
diff --git a/files/file_path.cc b/files/file_path.cc index de8927a..10dcf94 100644 --- a/files/file_path.cc +++ b/files/file_path.cc
@@ -10,9 +10,6 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/pickle.h" - -// These includes are just for the *Hack functions, and should be removed -// when those functions are removed. #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h" @@ -31,7 +28,8 @@ namespace base { -typedef FilePath::StringType StringType; +using StringType = FilePath::StringType; +using StringPieceType = FilePath::StringPieceType; namespace { @@ -45,7 +43,7 @@ // otherwise returns npos. This can only be true on Windows, when a pathname // begins with a letter followed by a colon. On other platforms, this always // returns npos. -StringType::size_type FindDriveLetter(const StringType& path) { +StringPieceType::size_type FindDriveLetter(StringPieceType path) { #if defined(FILE_PATH_USES_DRIVE_LETTERS) // This is dependent on an ASCII-based character set, but that's a // reasonable assumption. iswalpha can be too inclusive here. @@ -59,26 +57,25 @@ } #if defined(FILE_PATH_USES_DRIVE_LETTERS) -bool EqualDriveLetterCaseInsensitive(const StringType& a, - const StringType& b) { +bool EqualDriveLetterCaseInsensitive(StringPieceType a, StringPieceType b) { size_t a_letter_pos = FindDriveLetter(a); size_t b_letter_pos = FindDriveLetter(b); if (a_letter_pos == StringType::npos || b_letter_pos == StringType::npos) return a == b; - StringType a_letter(a.substr(0, a_letter_pos + 1)); - StringType b_letter(b.substr(0, b_letter_pos + 1)); - if (!StartsWith(a_letter, b_letter, false)) + StringPieceType a_letter(a.substr(0, a_letter_pos + 1)); + StringPieceType b_letter(b.substr(0, b_letter_pos + 1)); + if (!StartsWith(a_letter, b_letter, CompareCase::INSENSITIVE_ASCII)) return false; - StringType a_rest(a.substr(a_letter_pos + 1)); - StringType b_rest(b.substr(b_letter_pos + 1)); + StringPieceType a_rest(a.substr(a_letter_pos + 1)); + StringPieceType b_rest(b.substr(b_letter_pos + 1)); return a_rest == b_rest; } #endif // defined(FILE_PATH_USES_DRIVE_LETTERS) -bool IsPathAbsolute(const StringType& path) { +bool IsPathAbsolute(StringPieceType path) { #if defined(FILE_PATH_USES_DRIVE_LETTERS) StringType::size_type letter = FindDriveLetter(path); if (letter != StringType::npos) { @@ -177,7 +174,8 @@ FilePath::FilePath(const FilePath& that) : path_(that.path_) { } -FilePath::FilePath(const StringType& path) : path_(path) { +FilePath::FilePath(StringPieceType path) { + path.CopyToString(&path_); StringType::size_type nul_pos = path_.find(kStringTerminator); if (nul_pos != StringType::npos) path_.erase(nul_pos, StringType::npos); @@ -279,7 +277,7 @@ // never case sensitive. if ((FindDriveLetter(*parent_comp) != StringType::npos) && (FindDriveLetter(*child_comp) != StringType::npos)) { - if (!StartsWith(*parent_comp, *child_comp, false)) + if (!StartsWith(*parent_comp, *child_comp, CompareCase::INSENSITIVE_ASCII)) return false; ++parent_comp; ++child_comp; @@ -404,7 +402,7 @@ return FilePath(path_.substr(0, dot)); } -FilePath FilePath::InsertBeforeExtension(const StringType& suffix) const { +FilePath FilePath::InsertBeforeExtension(StringPieceType suffix) const { if (suffix.empty()) return FilePath(path_); @@ -413,27 +411,27 @@ StringType ext = Extension(); StringType ret = RemoveExtension().value(); - ret.append(suffix); + suffix.AppendToString(&ret); ret.append(ext); return FilePath(ret); } -FilePath FilePath::InsertBeforeExtensionASCII(const StringPiece& suffix) - const { +FilePath FilePath::InsertBeforeExtensionASCII(StringPiece suffix) const { DCHECK(IsStringASCII(suffix)); #if defined(OS_WIN) - return InsertBeforeExtension(ASCIIToUTF16(suffix.as_string())); + return InsertBeforeExtension(ASCIIToUTF16(suffix)); #elif defined(OS_POSIX) - return InsertBeforeExtension(suffix.as_string()); + return InsertBeforeExtension(suffix); #endif } -FilePath FilePath::AddExtension(const StringType& extension) const { +FilePath FilePath::AddExtension(StringPieceType extension) const { if (IsEmptyOrSpecialCase(BaseName().value())) return FilePath(); // If the new extension is "" or ".", then just return the current FilePath. - if (extension.empty() || extension == StringType(1, kExtensionSeparator)) + if (extension.empty() || + (extension.size() == 1 && extension[0] == kExtensionSeparator)) return *this; StringType str = path_; @@ -441,27 +439,28 @@ *(str.end() - 1) != kExtensionSeparator) { str.append(1, kExtensionSeparator); } - str.append(extension); + extension.AppendToString(&str); return FilePath(str); } -FilePath FilePath::ReplaceExtension(const StringType& extension) const { +FilePath FilePath::ReplaceExtension(StringPieceType extension) const { if (IsEmptyOrSpecialCase(BaseName().value())) return FilePath(); FilePath no_ext = RemoveExtension(); // If the new extension is "" or ".", then just remove the current extension. - if (extension.empty() || extension == StringType(1, kExtensionSeparator)) + if (extension.empty() || + (extension.size() == 1 && extension[0] == kExtensionSeparator)) return no_ext; StringType str = no_ext.value(); if (extension[0] != kExtensionSeparator) str.append(1, kExtensionSeparator); - str.append(extension); + extension.AppendToString(&str); return FilePath(str); } -bool FilePath::MatchesExtension(const StringType& extension) const { +bool FilePath::MatchesExtension(StringPieceType extension) const { DCHECK(extension.empty() || extension[0] == kExtensionSeparator); StringType current_extension = Extension(); @@ -472,17 +471,17 @@ return FilePath::CompareEqualIgnoreCase(extension, current_extension); } -FilePath FilePath::Append(const StringType& component) const { - const StringType* appended = &component; +FilePath FilePath::Append(StringPieceType component) const { + StringPieceType appended = component; StringType without_nuls; StringType::size_type nul_pos = component.find(kStringTerminator); - if (nul_pos != StringType::npos) { - without_nuls = component.substr(0, nul_pos); - appended = &without_nuls; + if (nul_pos != StringPieceType::npos) { + component.substr(0, nul_pos).CopyToString(&without_nuls); + appended = StringPieceType(without_nuls); } - DCHECK(!IsPathAbsolute(*appended)); + DCHECK(!IsPathAbsolute(appended)); if (path_.compare(kCurrentDirectory) == 0) { // Append normally doesn't do any normalization, but as a special case, @@ -492,7 +491,7 @@ // it's likely in practice to wind up with FilePath objects containing // only kCurrentDirectory when calling DirName on a single relative path // component. - return FilePath(*appended); + return FilePath(appended); } FilePath new_path(path_); @@ -501,7 +500,7 @@ // Don't append a separator if the path is empty (indicating the current // directory) or if the path component is empty (indicating nothing to // append). - if (appended->length() > 0 && new_path.path_.length() > 0) { + if (appended.length() > 0 && new_path.path_.length() > 0) { // Don't append a separator if the path still ends with a trailing // separator after stripping (indicating the root directory). if (!IsSeparator(new_path.path_[new_path.path_.length() - 1])) { @@ -512,7 +511,7 @@ } } - new_path.path_.append(*appended); + appended.AppendToString(&new_path.path_); return new_path; } @@ -520,12 +519,12 @@ return Append(component.value()); } -FilePath FilePath::AppendASCII(const StringPiece& component) const { +FilePath FilePath::AppendASCII(StringPiece component) const { DCHECK(base::IsStringASCII(component)); #if defined(OS_WIN) - return Append(ASCIIToUTF16(component.as_string())); + return Append(ASCIIToUTF16(component)); #elif defined(OS_POSIX) - return Append(component.as_string()); + return Append(component); #endif } @@ -680,17 +679,17 @@ } #if defined(OS_WIN) -// Windows specific implementation of file string comparisons +// Windows specific implementation of file string comparisons. -int FilePath::CompareIgnoreCase(const StringType& string1, - const StringType& string2) { +int FilePath::CompareIgnoreCase(StringPieceType string1, + StringPieceType string2) { // Perform character-wise upper case comparison rather than using the // fully Unicode-aware CompareString(). For details see: // http://blogs.msdn.com/michkap/archive/2005/10/17/481600.aspx - StringType::const_iterator i1 = string1.begin(); - StringType::const_iterator i2 = string2.begin(); - StringType::const_iterator string1end = string1.end(); - StringType::const_iterator string2end = string2.end(); + StringPieceType::const_iterator i1 = string1.begin(); + StringPieceType::const_iterator i2 = string2.begin(); + StringPieceType::const_iterator string1end = string1.end(); + StringPieceType::const_iterator string2end = string2.end(); for ( ; i1 != string1end && i2 != string2end; ++i1, ++i2) { wchar_t c1 = (wchar_t)LOWORD(::CharUpperW((LPWSTR)(DWORD_PTR)MAKELONG(*i1, 0))); @@ -709,7 +708,7 @@ } #elif defined(OS_MACOSX) -// Mac OS X specific implementation of file string comparisons +// Mac OS X specific implementation of file string comparisons. // cf. http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties // @@ -1153,25 +1152,23 @@ return codepoint; } -} // anonymous namespace +} // namespace // Special UTF-8 version of FastUnicodeCompare. Cf: // http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm // The input strings must be in the special HFS decomposed form. -int FilePath::HFSFastUnicodeCompare(const StringType& string1, - const StringType& string2) { +int FilePath::HFSFastUnicodeCompare(StringPieceType string1, + StringPieceType string2) { int length1 = string1.length(); int length2 = string2.length(); int index1 = 0; int index2 = 0; for (;;) { - int codepoint1 = HFSReadNextNonIgnorableCodepoint(string1.c_str(), - length1, - &index1); - int codepoint2 = HFSReadNextNonIgnorableCodepoint(string2.c_str(), - length2, - &index2); + int codepoint1 = + HFSReadNextNonIgnorableCodepoint(string1.data(), length1, &index1); + int codepoint2 = + HFSReadNextNonIgnorableCodepoint(string2.data(), length2, &index2); if (codepoint1 != codepoint2) return (codepoint1 < codepoint2) ? -1 : 1; if (codepoint1 == 0) { @@ -1182,15 +1179,10 @@ } } -StringType FilePath::GetHFSDecomposedForm(const StringType& string) { - ScopedCFTypeRef<CFStringRef> cfstring( - CFStringCreateWithBytesNoCopy( - NULL, - reinterpret_cast<const UInt8*>(string.c_str()), - string.length(), - kCFStringEncodingUTF8, - false, - kCFAllocatorNull)); +StringType FilePath::GetHFSDecomposedForm(StringPieceType string) { + ScopedCFTypeRef<CFStringRef> cfstring(CFStringCreateWithBytesNoCopy( + NULL, reinterpret_cast<const UInt8*>(string.data()), string.length(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); // Query the maximum length needed to store the result. In most cases this // will overestimate the required space. The return value also already // includes the space needed for a terminating 0. @@ -1215,8 +1207,8 @@ return result; } -int FilePath::CompareIgnoreCase(const StringType& string1, - const StringType& string2) { +int FilePath::CompareIgnoreCase(StringPieceType string1, + StringPieceType string2) { // Quick checks for empty strings - these speed things up a bit and make the // following code cleaner. if (string1.empty()) @@ -1230,22 +1222,12 @@ // GetHFSDecomposedForm() returns an empty string in an error case. if (hfs1.empty() || hfs2.empty()) { NOTREACHED(); - ScopedCFTypeRef<CFStringRef> cfstring1( - CFStringCreateWithBytesNoCopy( - NULL, - reinterpret_cast<const UInt8*>(string1.c_str()), - string1.length(), - kCFStringEncodingUTF8, - false, - kCFAllocatorNull)); - ScopedCFTypeRef<CFStringRef> cfstring2( - CFStringCreateWithBytesNoCopy( - NULL, - reinterpret_cast<const UInt8*>(string2.c_str()), - string2.length(), - kCFStringEncodingUTF8, - false, - kCFAllocatorNull)); + ScopedCFTypeRef<CFStringRef> cfstring1(CFStringCreateWithBytesNoCopy( + NULL, reinterpret_cast<const UInt8*>(string1.data()), string1.length(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); + ScopedCFTypeRef<CFStringRef> cfstring2(CFStringCreateWithBytesNoCopy( + NULL, reinterpret_cast<const UInt8*>(string2.data()), string2.length(), + kCFStringEncodingUTF8, false, kCFAllocatorNull)); return CFStringCompare(cfstring1, cfstring2, kCFCompareCaseInsensitive); @@ -1256,11 +1238,12 @@ #else // << WIN. MACOSX | other (POSIX) >> -// Generic (POSIX) implementation of file string comparison. -// TODO(rolandsteiner) check if this is sufficient/correct. -int FilePath::CompareIgnoreCase(const StringType& string1, - const StringType& string2) { - int comparison = strcasecmp(string1.c_str(), string2.c_str()); +// Generic Posix system comparisons. +int FilePath::CompareIgnoreCase(StringPieceType string1, + StringPieceType string2) { + // Specifically need null termianted strings for this API call. + int comparison = + strcasecmp(string1.as_string().c_str(), string2.as_string().c_str()); if (comparison < 0) return -1; if (comparison > 0)
diff --git a/files/file_path.h b/files/file_path.h index 0c84af6..f10503e 100644 --- a/files/file_path.h +++ b/files/file_path.h
@@ -112,7 +112,7 @@ #include "base/compiler_specific.h" #include "base/containers/hash_tables.h" #include "base/strings/string16.h" -#include "base/strings/string_piece.h" // For implicit conversions. +#include "base/strings/string_piece.h" #include "build/build_config.h" // Windows-style drive letter support and pathname separator characters can be @@ -144,6 +144,7 @@ typedef std::wstring StringType; #endif // OS_WIN + typedef BasicStringPiece<StringType> StringPieceType; typedef StringType::value_type CharType; // Null-terminated array of separators used to separate components in @@ -166,7 +167,7 @@ FilePath(); FilePath(const FilePath& that); - explicit FilePath(const StringType& path); + explicit FilePath(StringPieceType path); ~FilePath(); FilePath& operator=(const FilePath& that); @@ -267,26 +268,24 @@ // path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg" // path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)" // path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)" - FilePath InsertBeforeExtension( - const StringType& suffix) const WARN_UNUSED_RESULT; - FilePath InsertBeforeExtensionASCII( - const base::StringPiece& suffix) const WARN_UNUSED_RESULT; + FilePath InsertBeforeExtension(StringPieceType suffix) const + WARN_UNUSED_RESULT; + FilePath InsertBeforeExtensionASCII(StringPiece suffix) const + WARN_UNUSED_RESULT; // Adds |extension| to |file_name|. Returns the current FilePath if // |extension| is empty. Returns "" if BaseName() == "." or "..". - FilePath AddExtension( - const StringType& extension) const WARN_UNUSED_RESULT; + FilePath AddExtension(StringPieceType extension) const WARN_UNUSED_RESULT; // Replaces the extension of |file_name| with |extension|. If |file_name| // does not have an extension, then |extension| is added. If |extension| is // empty, then the extension is removed from |file_name|. // Returns "" if BaseName() == "." or "..". - FilePath ReplaceExtension( - const StringType& extension) const WARN_UNUSED_RESULT; + FilePath ReplaceExtension(StringPieceType extension) const WARN_UNUSED_RESULT; // Returns true if the file path matches the specified extension. The test is // case insensitive. Don't forget the leading period if appropriate. - bool MatchesExtension(const StringType& extension) const; + bool MatchesExtension(StringPieceType extension) const; // Returns a FilePath by appending a separator and the supplied path // component to this object's path. Append takes care to avoid adding @@ -294,7 +293,7 @@ // If this object's path is kCurrentDirectory, a new FilePath corresponding // only to |component| is returned. |component| must be a relative path; // it is an error to pass an absolute path. - FilePath Append(const StringType& component) const WARN_UNUSED_RESULT; + FilePath Append(StringPieceType component) const WARN_UNUSED_RESULT; FilePath Append(const FilePath& component) const WARN_UNUSED_RESULT; // Although Windows StringType is std::wstring, since the encoding it uses for @@ -303,8 +302,7 @@ // On Linux, although it can use any 8-bit encoding for paths, we assume that // ASCII is a valid subset, regardless of the encoding, since many operating // system paths will always be ASCII. - FilePath AppendASCII(const base::StringPiece& component) - const WARN_UNUSED_RESULT; + FilePath AppendASCII(StringPiece component) const WARN_UNUSED_RESULT; // Returns true if this FilePath contains an absolute path. On Windows, an // absolute path begins with either a drive letter specification followed by @@ -388,14 +386,14 @@ // on parts of a file path, e.g., just the extension. // CompareIgnoreCase() returns -1, 0 or 1 for less-than, equal-to and // greater-than respectively. - static int CompareIgnoreCase(const StringType& string1, - const StringType& string2); - static bool CompareEqualIgnoreCase(const StringType& string1, - const StringType& string2) { + static int CompareIgnoreCase(StringPieceType string1, + StringPieceType string2); + static bool CompareEqualIgnoreCase(StringPieceType string1, + StringPieceType string2) { return CompareIgnoreCase(string1, string2) == 0; } - static bool CompareLessIgnoreCase(const StringType& string1, - const StringType& string2) { + static bool CompareLessIgnoreCase(StringPieceType string1, + StringPieceType string2) { return CompareIgnoreCase(string1, string2) < 0; } @@ -405,14 +403,14 @@ // http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties // for further comments. // Returns the epmty string if the conversion failed. - static StringType GetHFSDecomposedForm(const FilePath::StringType& string); + static StringType GetHFSDecomposedForm(StringPieceType string); // Special UTF-8 version of FastUnicodeCompare. Cf: // http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm // IMPORTANT: The input strings must be in the special HFS decomposed form! // (cf. above GetHFSDecomposedForm method) - static int HFSFastUnicodeCompare(const StringType& string1, - const StringType& string2); + static int HFSFastUnicodeCompare(StringPieceType string1, + StringPieceType string2); #endif #if defined(OS_ANDROID)
diff --git a/files/file_posix.cc b/files/file_posix.cc index bb49d2d..7fb617c 100644 --- a/files/file_posix.cc +++ b/files/file_posix.cc
@@ -466,7 +466,7 @@ // NaCl doesn't implement system calls to open files directly. #if !defined(OS_NACL) // TODO(erikkay): does it make sense to support FLAG_EXCLUSIVE_* here? -void File::DoInitialize(uint32 flags) { +void File::DoInitialize(const FilePath& path, uint32 flags) { ThreadRestrictions::AssertIOAllowed(); DCHECK(!IsValid()); @@ -521,7 +521,7 @@ mode |= S_IRGRP | S_IROTH; #endif - int descriptor = HANDLE_EINTR(open(path_.value().c_str(), open_flags, mode)); + int descriptor = HANDLE_EINTR(open(path.value().c_str(), open_flags, mode)); if (flags & FLAG_OPEN_ALWAYS) { if (descriptor < 0) { @@ -529,7 +529,7 @@ if (flags & FLAG_EXCLUSIVE_READ || flags & FLAG_EXCLUSIVE_WRITE) open_flags |= O_EXCL; // together with O_CREAT implies O_NOFOLLOW - descriptor = HANDLE_EINTR(open(path_.value().c_str(), open_flags, mode)); + descriptor = HANDLE_EINTR(open(path.value().c_str(), open_flags, mode)); if (descriptor >= 0) created_ = true; } @@ -544,7 +544,7 @@ created_ = true; if (flags & FLAG_DELETE_ON_CLOSE) - unlink(path_.value().c_str()); + unlink(path.value().c_str()); async_ = ((flags & FLAG_ASYNC) == FLAG_ASYNC); error_details_ = FILE_OK;
diff --git a/files/file_tracing.cc b/files/file_tracing.cc index c25772d..92a5780 100644 --- a/files/file_tracing.cc +++ b/files/file_tracing.cc
@@ -13,6 +13,11 @@ } // static +bool FileTracing::IsCategoryEnabled() { + return g_provider && g_provider->FileTracingCategoryIsEnabled(); +} + +// static void FileTracing::SetProvider(FileTracing::Provider* provider) { g_provider = provider; } @@ -34,19 +39,11 @@ g_provider->FileTracingEventEnd(name_, id_); } -bool FileTracing::ScopedTrace::ShouldInitialize() const { - return g_provider && g_provider->FileTracingCategoryIsEnabled(); -} - void FileTracing::ScopedTrace::Initialize( const char* name, File* file, int64 size) { - if (!g_provider) - return; - id_ = &file->trace_enabler_; name_ = name; - - g_provider->FileTracingEventBegin(name_, id_, file->path_, size); + g_provider->FileTracingEventBegin(name_, id_, file->tracing_path_, size); } } // namespace base
diff --git a/files/file_tracing.h b/files/file_tracing.h index 149bd78..373fe0e 100644 --- a/files/file_tracing.h +++ b/files/file_tracing.h
@@ -12,9 +12,9 @@ #define FILE_TRACING_PREFIX "File" #define SCOPED_FILE_TRACE_WITH_SIZE(name, size) \ - FileTracing::ScopedTrace scoped_file_trace; \ - if (scoped_file_trace.ShouldInitialize()) \ - scoped_file_trace.Initialize(FILE_TRACING_PREFIX "::" name, this, size) + FileTracing::ScopedTrace scoped_file_trace; \ + if (FileTracing::IsCategoryEnabled()) \ + scoped_file_trace.Initialize(FILE_TRACING_PREFIX "::" name, this, size) #define SCOPED_FILE_TRACE(name) SCOPED_FILE_TRACE_WITH_SIZE(name, 0) @@ -25,8 +25,13 @@ class BASE_EXPORT FileTracing { public: + // Whether the file tracing category is enabled. + static bool IsCategoryEnabled(); + class Provider { public: + virtual ~Provider() = default; + // Whether the file tracing category is currently enabled. virtual bool FileTracingCategoryIsEnabled() const = 0; @@ -61,9 +66,6 @@ ScopedTrace(); ~ScopedTrace(); - // Whether this trace should be initialized or not. - bool ShouldInitialize() const; - // Called only if the tracing category is enabled. |name| is the name of the // event to trace (e.g. "Read", "Write") and must have an application // lifetime (e.g. static or literal). |file| is the file being traced; must
diff --git a/files/file_win.cc b/files/file_win.cc index 9792852..ccecb70 100644 --- a/files/file_win.cc +++ b/files/file_win.cc
@@ -308,7 +308,7 @@ } } -void File::DoInitialize(uint32 flags) { +void File::DoInitialize(const FilePath& path, uint32 flags) { ThreadRestrictions::AssertIOAllowed(); DCHECK(!IsValid()); @@ -376,8 +376,8 @@ if (flags & FLAG_BACKUP_SEMANTICS) create_flags |= FILE_FLAG_BACKUP_SEMANTICS; - file_.Set(CreateFile(path_.value().c_str(), access, sharing, NULL, - disposition, create_flags, NULL)); + file_.Set(CreateFile(path.value().c_str(), access, sharing, NULL, disposition, + create_flags, NULL)); if (file_.IsValid()) { error_details_ = FILE_OK;
diff --git a/files/memory_mapped_file.cc b/files/memory_mapped_file.cc index bad1792..227a41f 100644 --- a/files/memory_mapped_file.cc +++ b/files/memory_mapped_file.cc
@@ -10,18 +10,7 @@ namespace base { -const MemoryMappedFile::Region MemoryMappedFile::Region::kWholeFile( - base::LINKER_INITIALIZED); - -MemoryMappedFile::Region::Region(base::LinkerInitialized) : offset(0), size(0) { -} - -MemoryMappedFile::Region::Region() : offset(-1), size(-1) { -} - -MemoryMappedFile::Region::Region(int64 offset, int64 size) - : offset(offset), size(size) { -} +const MemoryMappedFile::Region MemoryMappedFile::Region::kWholeFile = {0, 0}; bool MemoryMappedFile::Region::operator==( const MemoryMappedFile::Region& other) const {
diff --git a/files/memory_mapped_file.h b/files/memory_mapped_file.h index 96d1d91..9ff29b9 100644 --- a/files/memory_mapped_file.h +++ b/files/memory_mapped_file.h
@@ -28,9 +28,6 @@ struct BASE_EXPORT Region { static const Region kWholeFile; - Region(); - Region(int64 offset, int64 size); - bool operator==(const Region& other) const; bool operator!=(const Region& other) const; @@ -39,10 +36,6 @@ // Length of the region in bytes. int64 size; - - private: - // Used by kWholeFile. - Region(base::LinkerInitialized); }; // Opens an existing file and maps it into memory. Access is restricted to
diff --git a/files/memory_mapped_file_unittest.cc b/files/memory_mapped_file_unittest.cc index d0833b5..05b941c 100644 --- a/files/memory_mapped_file_unittest.cc +++ b/files/memory_mapped_file_unittest.cc
@@ -107,7 +107,8 @@ MemoryMappedFile map; File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ); - map.Initialize(file.Pass(), MemoryMappedFile::Region(0, kPartialSize)); + MemoryMappedFile::Region region = {0, kPartialSize}; + map.Initialize(file.Pass(), region); ASSERT_EQ(kPartialSize, map.length()); ASSERT_TRUE(map.data() != NULL); EXPECT_TRUE(map.IsValid()); @@ -122,7 +123,8 @@ MemoryMappedFile map; File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ); - map.Initialize(file.Pass(), MemoryMappedFile::Region(kOffset, kPartialSize)); + MemoryMappedFile::Region region = {kOffset, kPartialSize}; + map.Initialize(file.Pass(), region); ASSERT_EQ(kPartialSize, map.length()); ASSERT_TRUE(map.data() != NULL); EXPECT_TRUE(map.IsValid()); @@ -138,7 +140,8 @@ MemoryMappedFile map; File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ); - map.Initialize(file.Pass(), MemoryMappedFile::Region(kOffset, kPartialSize)); + MemoryMappedFile::Region region = {kOffset, kPartialSize}; + map.Initialize(file.Pass(), region); ASSERT_EQ(kPartialSize, map.length()); ASSERT_TRUE(map.data() != NULL); EXPECT_TRUE(map.IsValid()); @@ -154,7 +157,8 @@ MemoryMappedFile map; File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ); - map.Initialize(file.Pass(), MemoryMappedFile::Region(kOffset, kPartialSize)); + MemoryMappedFile::Region region = {kOffset, kPartialSize}; + map.Initialize(file.Pass(), region); ASSERT_EQ(kPartialSize, map.length()); ASSERT_TRUE(map.data() != NULL); EXPECT_TRUE(map.IsValid());
diff --git a/files/memory_mapped_file_win.cc b/files/memory_mapped_file_win.cc index 4e7e934..8585906 100644 --- a/files/memory_mapped_file_win.cc +++ b/files/memory_mapped_file_win.cc
@@ -32,7 +32,7 @@ if (!file_mapping_.IsValid()) return false; - LARGE_INTEGER map_start = {0}; + LARGE_INTEGER map_start = {}; SIZE_T map_size = 0; int32 data_offset = 0;
diff --git a/i18n/bidi_line_iterator.cc b/i18n/bidi_line_iterator.cc index 8c81d85..80da731 100644 --- a/i18n/bidi_line_iterator.cc +++ b/i18n/bidi_line_iterator.cc
@@ -9,6 +9,25 @@ namespace base { namespace i18n { +namespace { +UBiDiLevel GetParagraphLevelForDirection(TextDirection direction) { + switch (direction) { + case UNKNOWN_DIRECTION: + return UBIDI_DEFAULT_LTR; + break; + case RIGHT_TO_LEFT: + return 1; // Highest RTL level. + break; + case LEFT_TO_RIGHT: + return 0; // Highest LTR level. + break; + default: + NOTREACHED(); + return 0; + } +} +} // namespace + BiDiLineIterator::BiDiLineIterator() : bidi_(NULL) { } @@ -19,15 +38,14 @@ } } -bool BiDiLineIterator::Open(const string16& text, bool right_to_left) { +bool BiDiLineIterator::Open(const string16& text, TextDirection direction) { DCHECK(!bidi_); UErrorCode error = U_ZERO_ERROR; bidi_ = ubidi_openSized(static_cast<int>(text.length()), 0, &error); if (U_FAILURE(error)) return false; ubidi_setPara(bidi_, text.data(), static_cast<int>(text.length()), - right_to_left ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, - NULL, &error); + GetParagraphLevelForDirection(direction), NULL, &error); return (U_SUCCESS(error) == TRUE); }
diff --git a/i18n/bidi_line_iterator.h b/i18n/bidi_line_iterator.h index 07d9aa2..0bf1ec6 100644 --- a/i18n/bidi_line_iterator.h +++ b/i18n/bidi_line_iterator.h
@@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "base/i18n/base_i18n_export.h" +#include "base/i18n/rtl.h" #include "base/strings/string16.h" #include "third_party/icu/source/common/unicode/ubidi.h" @@ -23,7 +24,7 @@ // Initializes the bidirectional iterator with the specified text. Returns // whether initialization succeeded. - bool Open(const string16& text, bool right_to_left); + bool Open(const string16& text, TextDirection direction); // Returns the number of visual runs in the text, or zero on error. int CountRuns();
diff --git a/i18n/case_conversion.cc b/i18n/case_conversion.cc index 5debc2e..d0eceb9 100644 --- a/i18n/case_conversion.cc +++ b/i18n/case_conversion.cc
@@ -4,22 +4,91 @@ #include "base/i18n/case_conversion.h" +#include "base/numerics/safe_conversions.h" #include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "third_party/icu/source/common/unicode/uchar.h" #include "third_party/icu/source/common/unicode/unistr.h" +#include "third_party/icu/source/common/unicode/ustring.h" namespace base { namespace i18n { -string16 ToLower(const StringPiece16& string) { - icu::UnicodeString unicode_string(string.data(), string.size()); - unicode_string.toLower(); - return string16(unicode_string.getBuffer(), unicode_string.length()); +namespace { + +// Provides a uniform interface for upper/lower/folding which take take +// slightly varying parameters. +typedef int32_t (*CaseMapperFunction)(UChar* dest, + int32_t dest_capacity, + const UChar* src, + int32_t src_length, + UErrorCode* error); + +int32_t ToUpperMapper(UChar* dest, + int32_t dest_capacity, + const UChar* src, + int32_t src_length, + UErrorCode* error) { + // Use default locale. + return u_strToUpper(dest, dest_capacity, src, src_length, NULL, error); } -string16 ToUpper(const StringPiece16& string) { - icu::UnicodeString unicode_string(string.data(), string.size()); - unicode_string.toUpper(); - return string16(unicode_string.getBuffer(), unicode_string.length()); +int32_t ToLowerMapper(UChar* dest, + int32_t dest_capacity, + const UChar* src, + int32_t src_length, + UErrorCode* error) { + // Use default locale. + return u_strToLower(dest, dest_capacity, src, src_length, NULL, error); +} + +int32_t FoldCaseMapper(UChar* dest, + int32_t dest_capacity, + const UChar* src, + int32_t src_length, + UErrorCode* error) { + return u_strFoldCase(dest, dest_capacity, src, src_length, + U_FOLD_CASE_DEFAULT, error); +} + +// Provides similar functionality as UnicodeString::caseMap but on string16. +string16 CaseMap(StringPiece16 string, CaseMapperFunction case_mapper) { + string16 dest; + if (string.empty()) + return dest; + + // Provide an initial guess that the string length won't change. The typical + // strings we use will very rarely change length in this process, so don't + // optimize for that case. + dest.resize(string.size()); + + UErrorCode error; + do { + error = U_ZERO_ERROR; + + // ICU won't terminate the string if there's not enough room for the null + // terminator, but will otherwise. So we don't need to save room for that. + // Don't use WriteInto, which assumes null terminators. + int32_t new_length = case_mapper( + &dest[0], saturated_cast<int32_t>(dest.size()), string.data(), + saturated_cast<int32_t>(string.size()), &error); + dest.resize(new_length); + } while (error == U_BUFFER_OVERFLOW_ERROR); + return dest; +} + +} // namespace + +string16 ToLower(StringPiece16 string) { + return CaseMap(string, &ToLowerMapper); +} + +string16 ToUpper(StringPiece16 string) { + return CaseMap(string, &ToUpperMapper); +} + +string16 FoldCase(StringPiece16 string) { + return CaseMap(string, &FoldCaseMapper); } } // namespace i18n
diff --git a/i18n/case_conversion.h b/i18n/case_conversion.h index 5d538cc..0631a80 100644 --- a/i18n/case_conversion.h +++ b/i18n/case_conversion.h
@@ -12,11 +12,35 @@ namespace base { namespace i18n { -// Returns the lower case equivalent of string. Uses ICU's default locale. -BASE_I18N_EXPORT string16 ToLower(const StringPiece16& string); +// UNICODE CASE-HANDLING ADVICE +// +// In English it's always safe to convert to upper-case or lower-case text +// and get a good answer. But some languages have rules specific to those +// locales. One example is the Turkish I: +// http://www.i18nguy.com/unicode/turkish-i18n.html +// +// ToLower/ToUpper use the current ICU locale which will take into account +// the user language preference. Use this when dealing with user typing. +// +// FoldCase canonicalizes to a standardized form independent of the current +// locale. Use this when comparing general Unicode strings that don't +// necessarily belong in the user's current locale (like commands, protocol +// names, other strings from the web) for case-insensitive equality. +// +// Note that case conversions will change the length of the string in some +// not-uncommon cases. Never assume that the output is the same length as +// the input. -// Returns the upper case equivalent of string. Uses ICU's default locale. -BASE_I18N_EXPORT string16 ToUpper(const StringPiece16& string); +// Returns the lower case equivalent of string. Uses ICU's current locale. +BASE_I18N_EXPORT string16 ToLower(StringPiece16 string); + +// Returns the upper case equivalent of string. Uses ICU's current locale. +BASE_I18N_EXPORT string16 ToUpper(StringPiece16 string); + +// Convert the given string to a canonical case, independent of the current +// locale. For ASCII the canonical form is lower case. +// See http://unicode.org/faq/casemap_charprop.html#2 +BASE_I18N_EXPORT string16 FoldCase(StringPiece16 string); } // namespace i18n } // namespace base
diff --git a/i18n/case_conversion_unittest.cc b/i18n/case_conversion_unittest.cc index 75e5ad2..dc5bc1f 100644 --- a/i18n/case_conversion_unittest.cc +++ b/i18n/case_conversion_unittest.cc
@@ -9,39 +9,47 @@ #include "third_party/icu/source/i18n/unicode/usearch.h" namespace base { +namespace i18n { + namespace { +const wchar_t kNonASCIIMixed[] = + L"\xC4\xD6\xE4\xF6\x20\xCF\xEF\x20\xF7\x25" + L"\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F07\x1F0F" + L"\x20\x1E00\x1E01"; +const wchar_t kNonASCIILower[] = + L"\xE4\xF6\xE4\xF6\x20\xEF\xEF" + L"\x20\xF7\x25\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F07" + L"\x1F07\x20\x1E01\x1E01"; +const wchar_t kNonASCIIUpper[] = + L"\xC4\xD6\xC4\xD6\x20\xCF\xCF" + L"\x20\xF7\x25\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F0F" + L"\x1F0F\x20\x1E00\x1E00"; + +} // namespace + // Test upper and lower case string conversion. TEST(CaseConversionTest, UpperLower) { const string16 mixed(ASCIIToUTF16("Text with UPPer & lowER casE.")); const string16 expected_lower(ASCIIToUTF16("text with upper & lower case.")); const string16 expected_upper(ASCIIToUTF16("TEXT WITH UPPER & LOWER CASE.")); - string16 result = base::i18n::ToLower(mixed); + string16 result = ToLower(mixed); EXPECT_EQ(expected_lower, result); - result = base::i18n::ToUpper(mixed); + result = ToUpper(mixed); EXPECT_EQ(expected_upper, result); } TEST(CaseConversionTest, NonASCII) { - const string16 mixed(WideToUTF16( - L"\xC4\xD6\xE4\xF6\x20\xCF\xEF\x20\xF7\x25" - L"\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F07\x1F0F" - L"\x20\x1E00\x1E01")); - const string16 expected_lower(WideToUTF16( - L"\xE4\xF6\xE4\xF6\x20\xEF\xEF" - L"\x20\xF7\x25\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F07" - L"\x1F07\x20\x1E01\x1E01")); - const string16 expected_upper(WideToUTF16( - L"\xC4\xD6\xC4\xD6\x20\xCF\xCF" - L"\x20\xF7\x25\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F0F" - L"\x1F0F\x20\x1E00\x1E00")); + const string16 mixed(WideToUTF16(kNonASCIIMixed)); + const string16 expected_lower(WideToUTF16(kNonASCIILower)); + const string16 expected_upper(WideToUTF16(kNonASCIIUpper)); - string16 result = base::i18n::ToLower(mixed); + string16 result = ToLower(mixed); EXPECT_EQ(expected_lower, result); - result = base::i18n::ToUpper(mixed); + result = ToUpper(mixed); EXPECT_EQ(expected_upper, result); } @@ -53,10 +61,10 @@ std::string default_locale(uloc_getDefault()); i18n::SetICUDefaultLocale("en_US"); - string16 result = base::i18n::ToLower(mixed); + string16 result = ToLower(mixed); EXPECT_EQ(expected_lower, result); - result = base::i18n::ToUpper(mixed); + result = ToUpper(mixed); EXPECT_EQ(expected_upper, result); i18n::SetICUDefaultLocale("tr"); @@ -64,16 +72,48 @@ const string16 expected_lower_turkish(WideToUTF16(L"\x131\x131")); const string16 expected_upper_turkish(WideToUTF16(L"\x49\x49")); - result = base::i18n::ToLower(mixed); + result = ToLower(mixed); EXPECT_EQ(expected_lower_turkish, result); - result = base::i18n::ToUpper(mixed); + result = ToUpper(mixed); EXPECT_EQ(expected_upper_turkish, result); - base::i18n::SetICUDefaultLocale(default_locale.data()); + SetICUDefaultLocale(default_locale.data()); } -} // namespace +TEST(CaseConversionTest, FoldCase) { + // Simple ASCII, should lower-case. + EXPECT_EQ(ASCIIToUTF16("hello, world"), + FoldCase(ASCIIToUTF16("Hello, World"))); + + // Non-ASCII cases from above. They should all fold to the same result. + EXPECT_EQ(FoldCase(WideToUTF16(kNonASCIIMixed)), + FoldCase(WideToUTF16(kNonASCIILower))); + EXPECT_EQ(FoldCase(WideToUTF16(kNonASCIIMixed)), + FoldCase(WideToUTF16(kNonASCIIUpper))); + + // Turkish cases from above. This is the lower-case expected result from the + // US locale. It should be the same even when the current locale is Turkish. + const string16 turkish(WideToUTF16(L"\x49\x131")); + const string16 turkish_expected(WideToUTF16(L"\x69\x131")); + + std::string default_locale(uloc_getDefault()); + i18n::SetICUDefaultLocale("en_US"); + EXPECT_EQ(turkish_expected, FoldCase(turkish)); + + i18n::SetICUDefaultLocale("tr"); + EXPECT_EQ(turkish_expected, FoldCase(turkish)); + + // Test a case that gets bigger when processed. + // U+130 = LATIN CAPITAL LETTER I WITH DOT ABOVE gets folded to a lower case + // "i" followed by U+307 COMBINING DOT ABOVE. + EXPECT_EQ(WideToUTF16(L"i\u0307j"), FoldCase(WideToUTF16(L"\u0130j"))); + + // U+00DF (SHARP S) and U+1E9E (CAPIRAL SHARP S) are both folded to "ss". + EXPECT_EQ(ASCIIToUTF16("ssss"), FoldCase(WideToUTF16(L"\u00DF\u1E9E"))); +} + +} // namespace i18n } // namespace base
diff --git a/i18n/icu_util.cc b/i18n/icu_util.cc index a9f0b12..1dd54cd 100644 --- a/i18n/icu_util.cc +++ b/i18n/icu_util.cc
@@ -23,6 +23,10 @@ #include "third_party/icu/source/i18n/unicode/timezone.h" #endif +#if defined(OS_ANDROID) +#include "base/android/apk_assets.h" +#endif + #if defined(OS_MACOSX) #include "base/mac/foundation_util.h" #endif @@ -34,11 +38,6 @@ namespace base { namespace i18n { -// Use an unversioned file name to simplify a icu version update down the road. -// No need to change the filename in multiple places (gyp files, windows -// build pkg configurations, etc). 'l' stands for Little Endian. -// This variable is exported through the header file. -const char kIcuDataFileName[] = "icudtl.dat"; #if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_SHARED #define ICU_UTIL_DATA_SYMBOL "icudt" U_ICU_VERSION_SHORT "_dat" #if defined(OS_WIN) @@ -47,44 +46,140 @@ #endif namespace { - +#if !defined(OS_NACL) #if !defined(NDEBUG) // Assert that we are not called more than once. Even though calling this // function isn't harmful (ICU can handle it), being called twice probably // indicates a programming error. -#if !defined(OS_NACL) -bool g_called_once = false; -#endif bool g_check_called_once = true; +bool g_called_once = false; +#endif // !defined(NDEBUG) + +#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE +// Use an unversioned file name to simplify a icu version update down the road. +// No need to change the filename in multiple places (gyp files, windows +// build pkg configurations, etc). 'l' stands for Little Endian. +// This variable is exported through the header file. +const char kIcuDataFileName[] = "icudtl.dat"; +#if defined(OS_ANDROID) +const char kAndroidAssetsIcuDataFileName[] = "assets/icudtl.dat"; #endif + +// File handle intentionally never closed. Not using File here because its +// Windows implementation guards against two instances owning the same +// PlatformFile (which we allow since we know it is never freed). +const PlatformFile kInvalidPlatformFile = +#if defined(OS_WIN) + INVALID_HANDLE_VALUE; +#else + -1; +#endif +PlatformFile g_icudtl_pf = kInvalidPlatformFile; +MemoryMappedFile* g_icudtl_mapped_file = nullptr; +MemoryMappedFile::Region g_icudtl_region; + +void LazyInitIcuDataFile() { + if (g_icudtl_pf != kInvalidPlatformFile) { + return; + } +#if defined(OS_ANDROID) + int fd = base::android::OpenApkAsset(kAndroidAssetsIcuDataFileName, + &g_icudtl_region); + g_icudtl_pf = fd; + if (fd != -1) { + return; + } +// For unit tests, data file is located on disk, so try there as a fallback. +#endif // defined(OS_ANDROID) +#if !defined(OS_MACOSX) + FilePath data_path; +#if defined(OS_WIN) + // The data file will be in the same directory as the current module. + bool path_ok = PathService::Get(DIR_MODULE, &data_path); + wchar_t tmp_buffer[_MAX_PATH] = {0}; + wcscpy_s(tmp_buffer, data_path.value().c_str()); + debug::Alias(tmp_buffer); + CHECK(path_ok); // TODO(scottmg): http://crbug.com/445616 +#elif defined(OS_ANDROID) + bool path_ok = PathService::Get(DIR_ANDROID_APP_DATA, &data_path); +#else + // For now, expect the data file to be alongside the executable. + // This is sufficient while we work on unit tests, but will eventually + // likely live in a data directory. + bool path_ok = PathService::Get(DIR_EXE, &data_path); +#endif + DCHECK(path_ok); + data_path = data_path.AppendASCII(kIcuDataFileName); + +#if defined(OS_WIN) + // TODO(scottmg): http://crbug.com/445616 + wchar_t tmp_buffer2[_MAX_PATH] = {0}; + wcscpy_s(tmp_buffer2, data_path.value().c_str()); + debug::Alias(tmp_buffer2); +#endif + +#else + // Assume it is in the framework bundle's Resources directory. + ScopedCFTypeRef<CFStringRef> data_file_name( + SysUTF8ToCFStringRef(kIcuDataFileName)); + FilePath data_path = mac::PathForFrameworkBundleResource(data_file_name); + if (data_path.empty()) { + LOG(ERROR) << kIcuDataFileName << " not found in bundle"; + return; + } +#endif // !defined(OS_MACOSX) + File file(data_path, File::FLAG_OPEN | File::FLAG_READ); + if (file.IsValid()) { + g_icudtl_pf = file.TakePlatformFile(); + g_icudtl_region = MemoryMappedFile::Region::kWholeFile; + } } +bool InitializeICUWithFileDescriptorInternal( + PlatformFile data_fd, + const MemoryMappedFile::Region& data_region) { + // This can be called multiple times in tests. + if (g_icudtl_mapped_file) { + return true; + } + if (data_fd == kInvalidPlatformFile) { + return false; + } + + scoped_ptr<MemoryMappedFile> icudtl_mapped_file(new MemoryMappedFile()); + if (!icudtl_mapped_file->Initialize(File(data_fd), data_region)) { + LOG(ERROR) << "Couldn't mmap icu data file"; + return false; + } + g_icudtl_mapped_file = icudtl_mapped_file.release(); + + UErrorCode err = U_ZERO_ERROR; + udata_setCommonData(const_cast<uint8*>(g_icudtl_mapped_file->data()), &err); + return err == U_ZERO_ERROR; +} +#endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE +#endif // !defined(OS_NACL) + +} // namespace + #if !defined(OS_NACL) +#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE bool InitializeICUWithFileDescriptor( PlatformFile data_fd, - MemoryMappedFile::Region data_region) { + const MemoryMappedFile::Region& data_region) { #if !defined(NDEBUG) DCHECK(!g_check_called_once || !g_called_once); g_called_once = true; #endif - -#if (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_STATIC) - // The ICU data is statically linked. - return true; -#elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE) - CR_DEFINE_STATIC_LOCAL(MemoryMappedFile, mapped_file, ()); - if (!mapped_file.IsValid()) { - if (!mapped_file.Initialize(File(data_fd), data_region)) { - LOG(ERROR) << "Couldn't mmap icu data file"; - return false; - } - } - UErrorCode err = U_ZERO_ERROR; - udata_setCommonData(const_cast<uint8*>(mapped_file.data()), &err); - return err == U_ZERO_ERROR; -#endif // ICU_UTIL_DATA_FILE + return InitializeICUWithFileDescriptorInternal(data_fd, data_region); } +PlatformFile GetIcuDataFileHandle(MemoryMappedFile::Region* out_region) { + CHECK_NE(g_icudtl_pf, kInvalidPlatformFile); + *out_region = g_icudtl_region; + return g_icudtl_pf; +} +#endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE bool InitializeICU() { #if !defined(NDEBUG) @@ -123,60 +218,9 @@ // it is needed. This can fail if the process is sandboxed at that time. // Instead, we map the file in and hand off the data so the sandbox won't // cause any problems. - - // Chrome doesn't normally shut down ICU, so the mapped data shouldn't ever - // be released. - CR_DEFINE_STATIC_LOCAL(MemoryMappedFile, mapped_file, ()); - if (!mapped_file.IsValid()) { -#if !defined(OS_MACOSX) - FilePath data_path; -#if defined(OS_WIN) - // The data file will be in the same directory as the current module. - bool path_ok = PathService::Get(DIR_MODULE, &data_path); - wchar_t tmp_buffer[_MAX_PATH] = {0}; - wcscpy_s(tmp_buffer, data_path.value().c_str()); - debug::Alias(tmp_buffer); - CHECK(path_ok); // TODO(scottmg): http://crbug.com/445616 -#elif defined(OS_ANDROID) - bool path_ok = PathService::Get(DIR_ANDROID_APP_DATA, &data_path); -#else - // For now, expect the data file to be alongside the executable. - // This is sufficient while we work on unit tests, but will eventually - // likely live in a data directory. - bool path_ok = PathService::Get(DIR_EXE, &data_path); -#endif - DCHECK(path_ok); - data_path = data_path.AppendASCII(kIcuDataFileName); - -#if defined(OS_WIN) - // TODO(scottmg): http://crbug.com/445616 - wchar_t tmp_buffer2[_MAX_PATH] = {0}; - wcscpy_s(tmp_buffer2, data_path.value().c_str()); - debug::Alias(tmp_buffer2); -#endif - -#else - // Assume it is in the framework bundle's Resources directory. - ScopedCFTypeRef<CFStringRef> data_file_name( - SysUTF8ToCFStringRef(kIcuDataFileName)); - FilePath data_path = - mac::PathForFrameworkBundleResource(data_file_name); - if (data_path.empty()) { - LOG(ERROR) << kIcuDataFileName << " not found in bundle"; - return false; - } -#endif // OS check - if (!mapped_file.Initialize(data_path)) { -#if defined(OS_WIN) - CHECK(false); // TODO(scottmg): http://crbug.com/445616 -#endif - LOG(ERROR) << "Couldn't mmap " << data_path.AsUTF8Unsafe(); - return false; - } - } - UErrorCode err = U_ZERO_ERROR; - udata_setCommonData(const_cast<uint8*>(mapped_file.data()), &err); - result = (err == U_ZERO_ERROR); + LazyInitIcuDataFile(); + result = + InitializeICUWithFileDescriptorInternal(g_icudtl_pf, g_icudtl_region); #if defined(OS_WIN) CHECK(result); // TODO(scottmg): http://crbug.com/445616 #endif @@ -193,10 +237,10 @@ #endif return result; } -#endif +#endif // !defined(OS_NACL) void AllowMultipleInitializeCallsForTesting() { -#if !defined(NDEBUG) +#if !defined(NDEBUG) && !defined(OS_NACL) g_check_called_once = false; #endif }
diff --git a/i18n/icu_util.h b/i18n/icu_util.h index 65de0ad..1de4172 100644 --- a/i18n/icu_util.h +++ b/i18n/icu_util.h
@@ -12,19 +12,24 @@ namespace base { namespace i18n { -BASE_I18N_EXPORT extern const char kIcuDataFileName[]; - #if !defined(OS_NACL) // Call this function to load ICU's data tables for the current process. This // function should be called before ICU is used. BASE_I18N_EXPORT bool InitializeICU(); +#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE +// Returns the PlatformFile and Region that was initialized by InitializeICU(). +// Use with InitializeICUWithFileDescriptor(). +BASE_I18N_EXPORT PlatformFile +GetIcuDataFileHandle(MemoryMappedFile::Region* out_region); + // Android and html_viewer use a file descriptor passed by browser process to // initialize ICU in render processes. BASE_I18N_EXPORT bool InitializeICUWithFileDescriptor( PlatformFile data_fd, - MemoryMappedFile::Region data_region); -#endif + const MemoryMappedFile::Region& data_region); +#endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE +#endif // !defined(OS_NACL) // In a test binary, the call above might occur twice. BASE_I18N_EXPORT void AllowMultipleInitializeCallsForTesting();
diff --git a/i18n/time_formatting.cc b/i18n/time_formatting.cc index 15b34a3..5512111 100644 --- a/i18n/time_formatting.cc +++ b/i18n/time_formatting.cc
@@ -45,6 +45,26 @@ static_cast<size_t>(time_string.length())); } +icu::SimpleDateFormat CreateSimpleDateFormatter(const char* pattern) { + // Generate a locale-dependent format pattern. The generator will take + // care of locale-dependent formatting issues like which separator to + // use (some locales use '.' instead of ':'), and where to put the am/pm + // marker. + UErrorCode status = U_ZERO_ERROR; + scoped_ptr<icu::DateTimePatternGenerator> generator( + icu::DateTimePatternGenerator::createInstance(status)); + DCHECK(U_SUCCESS(status)); + icu::UnicodeString generated_pattern = + generator->getBestPattern(icu::UnicodeString(pattern), status); + DCHECK(U_SUCCESS(status)); + + // Then, format the time using the generated pattern. + icu::SimpleDateFormat formatter(generated_pattern, status); + DCHECK(U_SUCCESS(status)); + + return formatter; +} + } // namespace string16 TimeFormatTimeOfDay(const Time& time) { @@ -55,6 +75,11 @@ return TimeFormat(formatter.get(), time); } +string16 TimeFormatTimeOfDayWithMilliseconds(const Time& time) { + icu::SimpleDateFormat formatter = CreateSimpleDateFormatter("HmsSSS"); + return TimeFormatWithoutAmPm(&formatter, time); +} + string16 TimeFormatTimeOfDayWithHourClockType(const Time& time, HourClockType type, AmPmClockType ampm) { @@ -65,22 +90,9 @@ return TimeFormatTimeOfDay(time); } - // Generate a locale-dependent format pattern. The generator will take - // care of locale-dependent formatting issues like which separator to - // use (some locales use '.' instead of ':'), and where to put the am/pm - // marker. - UErrorCode status = U_ZERO_ERROR; - scoped_ptr<icu::DateTimePatternGenerator> generator( - icu::DateTimePatternGenerator::createInstance(status)); - DCHECK(U_SUCCESS(status)); const char* base_pattern = (type == k12HourClock ? "ahm" : "Hm"); - icu::UnicodeString generated_pattern = - generator->getBestPattern(icu::UnicodeString(base_pattern), status); - DCHECK(U_SUCCESS(status)); + icu::SimpleDateFormat formatter = CreateSimpleDateFormatter(base_pattern); - // Then, format the time using the generated pattern. - icu::SimpleDateFormat formatter(generated_pattern, status); - DCHECK(U_SUCCESS(status)); if (ampm == kKeepAmPm) { return TimeFormat(&formatter, time); } else {
diff --git a/i18n/time_formatting.h b/i18n/time_formatting.h index 2053c0b..df91f41 100644 --- a/i18n/time_formatting.h +++ b/i18n/time_formatting.h
@@ -30,6 +30,10 @@ // Returns the time of day, e.g., "3:07 PM". BASE_I18N_EXPORT string16 TimeFormatTimeOfDay(const Time& time); +// Returns the time of day in 24-hour clock format with millisecond accuracy, +// e.g., "15:07:30.568" +BASE_I18N_EXPORT string16 TimeFormatTimeOfDayWithMilliseconds(const Time& time); + // Returns the time of day in the specified hour clock type. e.g. // "3:07 PM" (type == k12HourClock, ampm == kKeepAmPm). // "3:07" (type == k12HourClock, ampm == kDropAmPm).
diff --git a/i18n/time_formatting_unittest.cc b/i18n/time_formatting_unittest.cc index df0c1ed..9385628 100644 --- a/i18n/time_formatting_unittest.cc +++ b/i18n/time_formatting_unittest.cc
@@ -12,6 +12,7 @@ #include "third_party/icu/source/common/unicode/uversion.h" #include "third_party/icu/source/i18n/unicode/calendar.h" #include "third_party/icu/source/i18n/unicode/timezone.h" +#include "third_party/icu/source/i18n/unicode/tzfmt.h" namespace base { namespace { @@ -21,10 +22,19 @@ 15, 42, 7, 0 // 15:42:07.000 }; -base::string16 GetShortTimeZone() { +// Returns difference between the local time and GMT formatted as string. +// This function gets |time| because the difference depends on time, +// see https://en.wikipedia.org/wiki/Daylight_saving_time for details. +base::string16 GetShortTimeZone(const Time& time) { + UErrorCode status = U_ZERO_ERROR; scoped_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault()); + scoped_ptr<icu::TimeZoneFormat> zone_formatter( + icu::TimeZoneFormat::createInstance(icu::Locale::getDefault(), status)); + EXPECT_TRUE(U_SUCCESS(status)); icu::UnicodeString name; - zone->getDisplayName(true, icu::TimeZone::SHORT, name); + zone_formatter->format(UTZFMT_STYLE_SPECIFIC_SHORT, *zone, + static_cast<UDate>(time.ToDoubleT() * 1000), name, + nullptr); return base::string16(name.getBuffer(), name.length()); } @@ -37,9 +47,11 @@ string16 clock24h(ASCIIToUTF16("15:42")); string16 clock12h_pm(ASCIIToUTF16("3:42 PM")); string16 clock12h(ASCIIToUTF16("3:42")); + string16 clock24h_millis(ASCIIToUTF16("15:42:07.000")); // The default is 12h clock. EXPECT_EQ(clock12h_pm, TimeFormatTimeOfDay(time)); + EXPECT_EQ(clock24h_millis, TimeFormatTimeOfDayWithMilliseconds(time)); EXPECT_EQ(k12HourClock, GetHourClockType()); // k{Keep,Drop}AmPm should not affect for 24h clock. EXPECT_EQ(clock24h, @@ -70,9 +82,11 @@ string16 clock24h(ASCIIToUTF16("15:42")); string16 clock12h_pm(ASCIIToUTF16("3:42 pm")); string16 clock12h(ASCIIToUTF16("3:42")); + string16 clock24h_millis(ASCIIToUTF16("15:42:07.000")); // The default is 24h clock. EXPECT_EQ(clock24h, TimeFormatTimeOfDay(time)); + EXPECT_EQ(clock24h_millis, TimeFormatTimeOfDayWithMilliseconds(time)); EXPECT_EQ(k24HourClock, GetHourClockType()); // k{Keep,Drop}AmPm should not affect for 24h clock. EXPECT_EQ(clock24h, @@ -139,7 +153,7 @@ EXPECT_EQ(ASCIIToUTF16("4/30/11, 3:42:07 PM"), TimeFormatShortDateAndTime(time)); - EXPECT_EQ(ASCIIToUTF16("4/30/11, 3:42:07 PM ") + GetShortTimeZone(), + EXPECT_EQ(ASCIIToUTF16("4/30/11, 3:42:07 PM ") + GetShortTimeZone(time), TimeFormatShortDateAndTimeWithTimeZone(time)); EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011 at 3:42:07 PM"), @@ -160,7 +174,7 @@ EXPECT_EQ(ASCIIToUTF16("30/04/2011"), TimeFormatShortDateNumeric(time)); EXPECT_EQ(ASCIIToUTF16("30/04/2011, 15:42:07"), TimeFormatShortDateAndTime(time)); - EXPECT_EQ(ASCIIToUTF16("30/04/2011, 15:42:07 ") + GetShortTimeZone(), + EXPECT_EQ(ASCIIToUTF16("30/04/2011, 15:42:07 ") + GetShortTimeZone(time), TimeFormatShortDateAndTimeWithTimeZone(time)); EXPECT_EQ(ASCIIToUTF16("Saturday, 30 April 2011 at 15:42:07"), TimeFormatFriendlyDateAndTime(time));
diff --git a/ios/crb_protocol_observers_unittest.mm b/ios/crb_protocol_observers_unittest.mm index 5f11051..b8cf423 100644 --- a/ios/crb_protocol_observers_unittest.mm +++ b/ios/crb_protocol_observers_unittest.mm
@@ -4,6 +4,7 @@ #import "base/ios/crb_protocol_observers.h" #include "base/ios/weak_nsobject.h" +#include "base/logging.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/mac/scoped_nsobject.h" #include "testing/gtest/include/gtest/gtest.h" @@ -36,10 +37,9 @@ @end @interface TestMutateObserver : TestCompleteObserver - - (instancetype)initWithObserver:(CRBProtocolObservers*)observer NS_DESIGNATED_INITIALIZER; - +- (instancetype)init NS_UNAVAILABLE; @end namespace { @@ -266,6 +266,11 @@ return self; } +- (instancetype)init { + NOTREACHED(); + return nil; +} + - (void)mutateByAddingObserver:(id<TestObserver>)observer { [_observers addObserver:observer]; }
diff --git a/ios/device_util.mm b/ios/device_util.mm index 1234562..4af8234 100644 --- a/ios/device_util.mm +++ b/ios/device_util.mm
@@ -59,7 +59,7 @@ std::string platform; size_t size = 0; sysctlbyname("hw.machine", NULL, &size, NULL, 0); - sysctlbyname("hw.machine", WriteInto(&platform, size), &size, NULL, 0); + sysctlbyname("hw.machine", base::WriteInto(&platform, size), &size, NULL, 0); return platform; }
diff --git a/ios/ios_util.h b/ios/ios_util.h index d9d7e19..688fbf3 100644 --- a/ios/ios_util.h +++ b/ios/ios_util.h
@@ -14,6 +14,9 @@ // Returns whether the operating system is iOS 8 or later. BASE_EXPORT bool IsRunningOnIOS8OrLater(); +// Returns whether the operating system is iOS 9 or later. +BASE_EXPORT bool IsRunningOnIOS9OrLater(); + // Returns whether the operating system is at the given version or later. BASE_EXPORT bool IsRunningOnOrLater(int32 major, int32 minor, int32 bug_fix);
diff --git a/ios/ios_util.mm b/ios/ios_util.mm index ca0a24d..d920045 100644 --- a/ios/ios_util.mm +++ b/ios/ios_util.mm
@@ -20,10 +20,15 @@ namespace base { namespace ios { +// When dropping iOS7 support, also address issues listed in crbug.com/502968. bool IsRunningOnIOS8OrLater() { return IsRunningOnOrLater(8, 0, 0); } +bool IsRunningOnIOS9OrLater() { + return IsRunningOnOrLater(9, 0, 0); +} + bool IsRunningOnOrLater(int32 major, int32 minor, int32 bug_fix) { static const int32* current_version = OSVersionAsArray(); int32 version[] = { major, minor, bug_fix };
diff --git a/ios/ns_error_util.h b/ios/ns_error_util.h new file mode 100644 index 0000000..1012292 --- /dev/null +++ b/ios/ns_error_util.h
@@ -0,0 +1,25 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_IOS_NS_ERROR_UTIL_H_ +#define BASE_IOS_NS_ERROR_UTIL_H_ + +@class NSError; + +namespace base { +namespace ios { + +// Iterates through |error|'s underlying errors and returns the first error for +// which there is no underlying error. +NSError* GetFinalUnderlyingErrorFromError(NSError* error); + +// Returns a copy of |original_error| with |underlying_error| appended to the +// end of its underlying error chain. +NSError* ErrorWithAppendedUnderlyingError(NSError* original_error, + NSError* underlying_error); + +} // namespace ios +} // namespace base + +#endif // BASE_IOS_NS_ERROR_UTIL_H_
diff --git a/ios/ns_error_util.mm b/ios/ns_error_util.mm new file mode 100644 index 0000000..c44d9ee --- /dev/null +++ b/ios/ns_error_util.mm
@@ -0,0 +1,53 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "base/ios/ns_error_util.h" + +#import <Foundation/Foundation.h> + +#include "base/logging.h" +#include "base/mac/scoped_nsobject.h" + +namespace base { +namespace ios { + +namespace { +// Iterates through |error|'s underlying errors and returns them in an array. +NSArray* GetFullErrorChainForError(NSError* error) { + NSMutableArray* error_chain = [NSMutableArray array]; + NSError* current_error = error; + while (current_error) { + DCHECK([current_error isKindOfClass:[NSError class]]); + [error_chain addObject:current_error]; + current_error = current_error.userInfo[NSUnderlyingErrorKey]; + } + return error_chain; +} +} // namespace + +NSError* GetFinalUnderlyingErrorFromError(NSError* error) { + DCHECK(error); + return [GetFullErrorChainForError(error) lastObject]; +} + +NSError* ErrorWithAppendedUnderlyingError(NSError* original_error, + NSError* underlying_error) { + DCHECK(original_error); + DCHECK(underlying_error); + NSArray* error_chain = GetFullErrorChainForError(original_error); + NSError* current_error = underlying_error; + for (NSInteger idx = error_chain.count - 1; idx >= 0; --idx) { + NSError* error = error_chain[idx]; + scoped_nsobject<NSMutableDictionary> user_info( + [error.userInfo mutableCopy]); + [user_info setObject:current_error forKey:NSUnderlyingErrorKey]; + current_error = [NSError errorWithDomain:error.domain + code:error.code + userInfo:user_info]; + } + return current_error; +} + +} // namespace ios +} // namespace base
diff --git a/json/json_parser.cc b/json/json_parser.cc index 4d79be3..fc972ce 100644 --- a/json/json_parser.cc +++ b/json/json_parser.cc
@@ -31,7 +31,7 @@ // optimization avoids about 2/3rds of string memory copies. The constructor // takes ownership of the input string. The real root value is Swap()ed into // the new instance. -class DictionaryHiddenRootValue : public base::DictionaryValue { +class DictionaryHiddenRootValue : public DictionaryValue { public: DictionaryHiddenRootValue(std::string* json, Value* root) : json_(json) { DCHECK(root->IsType(Value::TYPE_DICTIONARY)); @@ -43,7 +43,7 @@ // First deep copy to convert JSONStringValue to std::string and swap that // copy with |other|, which contains the new contents of |this|. - scoped_ptr<base::DictionaryValue> copy(DeepCopy()); + scoped_ptr<DictionaryValue> copy(DeepCopy()); copy->Swap(other); // Then erase the contents of the current dictionary and swap in the @@ -81,7 +81,7 @@ DISALLOW_COPY_AND_ASSIGN(DictionaryHiddenRootValue); }; -class ListHiddenRootValue : public base::ListValue { +class ListHiddenRootValue : public ListValue { public: ListHiddenRootValue(std::string* json, Value* root) : json_(json) { DCHECK(root->IsType(Value::TYPE_LIST)); @@ -93,7 +93,7 @@ // First deep copy to convert JSONStringValue to std::string and swap that // copy with |other|, which contains the new contents of |this|. - scoped_ptr<base::ListValue> copy(DeepCopy()); + scoped_ptr<ListValue> copy(DeepCopy()); copy->Swap(other); // Then erase the contents of the current list and swap in the new contents, @@ -130,14 +130,12 @@ // A variant on StringValue that uses StringPiece instead of copying the string // into the Value. This can only be stored in a child of hidden root (above), // otherwise the referenced string will not be guaranteed to outlive it. -class JSONStringValue : public base::Value { +class JSONStringValue : public Value { public: - explicit JSONStringValue(const base::StringPiece& piece) - : Value(TYPE_STRING), - string_piece_(piece) { - } + explicit JSONStringValue(const StringPiece& piece) + : Value(TYPE_STRING), string_piece_(piece) {} - // Overridden from base::Value: + // Overridden from Value: bool GetAsString(std::string* out_value) const override { string_piece_.CopyToString(out_value); return true; @@ -157,7 +155,7 @@ private: // The location in the original input stream. - base::StringPiece string_piece_; + StringPiece string_piece_; DISALLOW_COPY_AND_ASSIGN(JSONStringValue); }; @@ -776,11 +774,17 @@ uint32 code_point = CBU16_GET_SUPPLEMENTARY(code_unit16_high, code_unit16_low); + if (!IsValidCharacter(code_point)) + return false; + offset = 0; CBU8_APPEND_UNSAFE(code_unit8, offset, code_point); } else { // Not a surrogate. DCHECK(CBU16_IS_SINGLE(code_unit16_high)); + if (!IsValidCharacter(code_unit16_high)) + return false; + CBU8_APPEND_UNSAFE(code_unit8, offset, code_unit16_high); } @@ -789,6 +793,8 @@ } void JSONParser::DecodeUTF8(const int32& point, StringBuilder* dest) { + DCHECK(IsValidCharacter(point)); + // Anything outside of the basic ASCII plane will need to be decoded from // int32 to a multi-byte sequence. if (point < kExtendedASCIIStart) { @@ -872,7 +878,7 @@ return new FundamentalValue(num_int); double num_double; - if (base::StringToDouble(num_string.as_string(), &num_double) && + if (StringToDouble(num_string.as_string(), &num_double) && std::isfinite(num_double)) { return new FundamentalValue(num_double); }
diff --git a/json/json_parser_unittest.cc b/json/json_parser_unittest.cc index f776ddf..d88f9ea 100644 --- a/json/json_parser_unittest.cc +++ b/json/json_parser_unittest.cc
@@ -313,5 +313,13 @@ EXPECT_TRUE(root.get()) << error_message; } +TEST_F(JSONParserTest, DecodeUnicodeNonCharacter) { + // Tests Unicode code points (encoded as escaped UTF-16) that are not valid + // characters. + EXPECT_FALSE(JSONReader::Read("[\"\\ufdd0\"]")); + EXPECT_FALSE(JSONReader::Read("[\"\\ufffe\"]")); + EXPECT_FALSE(JSONReader::Read("[\"\\ud83f\\udffe\"]")); +} + } // namespace internal } // namespace base
diff --git a/logging.cc b/logging.cc index 1a2f774..7654e7f 100644 --- a/logging.cc +++ b/logging.cc
@@ -521,6 +521,12 @@ Init(file, line); } +LogMessage::LogMessage(const char* file, int line, const char* condition) + : severity_(LOG_FATAL), file_(file), line_(line) { + Init(file, line); + stream_ << "Check failed: " << condition << ". "; +} + LogMessage::LogMessage(const char* file, int line, std::string* result) : severity_(LOG_FATAL), file_(file), line_(line) { Init(file, line);
diff --git a/logging.h b/logging.h index ea096d1..adc8fb6 100644 --- a/logging.h +++ b/logging.h
@@ -436,7 +436,7 @@ #if defined(OFFICIAL_BUILD) && defined(NDEBUG) && !defined(OS_ANDROID) // Make all CHECK functions discard their log strings to reduce code -// bloat for official release builds. +// bloat for official release builds (except Android). // TODO(akalin): This would be more valuable if there were some way to // remove BreakDebugger() from the backtrace, perhaps by turning it @@ -470,9 +470,10 @@ #else // _PREFAST_ -#define CHECK(condition) \ - LAZY_STREAM(LOG_STREAM(FATAL), !(condition)) \ - << "Check failed: " #condition ". " +// Do as much work as possible out of line to reduce inline code size. +#define CHECK(condition) \ + LAZY_STREAM(logging::LogMessage(__FILE__, __LINE__, #condition).stream(), \ + !(condition)) #define PCHECK(condition) \ LAZY_STREAM(PLOG_STREAM(FATAL), !(condition)) \ @@ -727,6 +728,9 @@ // Used for LOG(severity). LogMessage(const char* file, int line, LogSeverity severity); + // Used for CHECK(). Implied severity = LOG_FATAL. + LogMessage(const char* file, int line, const char* condition); + // Used for CHECK_EQ(), etc. Takes ownership of the given string. // Implied severity = LOG_FATAL. LogMessage(const char* file, int line, std::string* result);
diff --git a/mac/OWNERS b/mac/OWNERS index 4aba972..a3fc32f 100644 --- a/mac/OWNERS +++ b/mac/OWNERS
@@ -1,14 +1,2 @@ mark@chromium.org thakis@chromium.org - -# sdk_forward_declarations.[h|mm] will likely need to be modified by Cocoa -# developers in general; keep in sync with OWNERS of //chrome/browser/ui/cocoa. -per-file sdk_forward_declarations.*=asvitkine@chromium.org -per-file sdk_forward_declarations.*=avi@chromium.org -per-file sdk_forward_declarations.*=groby@chromium.org -per-file sdk_forward_declarations.*=jeremy@chromium.org -per-file sdk_forward_declarations.*=mark@chromium.org -per-file sdk_forward_declarations.*=rohitrao@chromium.org -per-file sdk_forward_declarations.*=rsesek@chromium.org -per-file sdk_forward_declarations.*=shess@chromium.org -per-file sdk_forward_declarations.*=thakis@chromium.org
diff --git a/mac/call_with_eh_frame.cc b/mac/call_with_eh_frame.cc new file mode 100644 index 0000000..8ea6f75 --- /dev/null +++ b/mac/call_with_eh_frame.cc
@@ -0,0 +1,37 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/mac/call_with_eh_frame.h" + +#include <unwind.h> + +#include "build/build_config.h" + +namespace base { +namespace mac { + +_Unwind_Reason_Code CxxPersonalityRoutine( + int version, + _Unwind_Action actions, + uint64_t exceptionClass, + struct _Unwind_Exception* exceptionObject, + struct _Unwind_Context* context) { + // Tell libunwind that this is the end of the stack. When it encounters the + // CallWithEHFrame, it will stop searching for an exception handler. The + // result is that no exception handler has been found higher on the stack, + // and any that are lower on the stack (e.g. in CFRunLoopRunSpecific), will + // now be skipped. Since this is reporting the end of the stack, and no + // exception handler will have been found, std::terminate() will be called. + return _URC_END_OF_STACK; +} + +#if defined(OS_IOS) +// No iOS assembly implementation exists, so just call the block directly. +void CallWithEHFrame(void (^block)(void)) { + block(); +} +#endif + +} // namespace mac +} // namespace base
diff --git a/mac/call_with_eh_frame.h b/mac/call_with_eh_frame.h new file mode 100644 index 0000000..1f7d5e0 --- /dev/null +++ b/mac/call_with_eh_frame.h
@@ -0,0 +1,26 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_CALL_WITH_EH_FRAME_H_ +#define BASE_MAC_CALL_WITH_EH_FRAME_H_ + +#include "base/base_export.h" + +namespace base { +namespace mac { + +// Invokes the specified block in a stack frame with a special exception +// handler. This function creates an exception handling stack frame that +// specifies a custom C++ exception personality routine, which terminates the +// search for an exception handler at this frame. +// +// The purpose of this function is to prevent a try/catch statement in system +// libraries, acting as a global exception handler, from handling exceptions +// in such a way that disrupts the generation of useful stack traces. +void BASE_EXPORT CallWithEHFrame(void (^block)(void)); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_CALL_WITH_EH_FRAME_H_
diff --git a/mac/call_with_eh_frame_asm.S b/mac/call_with_eh_frame_asm.S new file mode 100644 index 0000000..0e399cf --- /dev/null +++ b/mac/call_with_eh_frame_asm.S
@@ -0,0 +1,89 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// base::mac::CallWithEHFrame(void () block_pointer) +#define CALL_WITH_EH_FRAME __ZN4base3mac15CallWithEHFrameEU13block_pointerFvvE + + .section __TEXT,__text,regular,pure_instructions +#if !defined(COMPONENT_BUILD) + .private_extern CALL_WITH_EH_FRAME +#endif + .globl CALL_WITH_EH_FRAME + .align 4 +CALL_WITH_EH_FRAME: + + .cfi_startproc + + // Configure the C++ exception handler personality routine. Normally the + // compiler would emit ___gxx_personality_v0 here. The purpose of this + // function is to use a custom personality routine. + .cfi_personality 155, __ZN4base3mac21CxxPersonalityRoutineEi14_Unwind_ActionyP17_Unwind_ExceptionP15_Unwind_Context + .cfi_lsda 16, CallWithEHFrame_exception_table + +Lfunction_start: + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + + // Load the function pointer from the block descriptor. + movq 16(%rdi), %rax + + // Execute the block in the context of a C++ try{}. +Ltry_start: + callq *%rax +Ltry_end: + popq %rbp + ret + + // Landing pad for the exception handler. This should never be called, since + // the personality routine will stop the search for an exception handler, + // which will cause the runtime to invoke the default terminate handler. +Lcatch: + movq %rax, %rdi + callq ___cxa_begin_catch // The ABI requires a call to the catch handler. + ud2 // In the event this is called, make it fatal. + +Lfunction_end: + .cfi_endproc + +// The C++ exception table that is used to identify this frame as an +// exception handler. See http://llvm.org/docs/ExceptionHandling.html and +// http://mentorembedded.github.io/cxx-abi/exceptions.pdf. + .section __TEXT,__gcc_except_tab + .align 2 +CallWithEHFrame_exception_table: + .byte 255 // DW_EH_PE_omit + .byte 155 // DW_EH_PE_indirect | DW_EH_PE_pcrel | DW_EH_PE_sdata4 + .asciz "\242\200\200" // LE int128 for the number of bytes in this table. + .byte 3 // DW_EH_PE_udata4 + .byte 26 // Callsite table length. + +// First callsite. +CS1_begin = Ltry_start - Lfunction_start + .long CS1_begin +CS1_end = Ltry_end - Ltry_start + .long CS1_end + +// First landing pad. +LP1 = Lcatch - Lfunction_start + .long LP1 + .byte 1 // Action record. + +// Second callsite. +CS2_begin = Ltry_end - Lfunction_start + .long CS2_begin +CS2_end = Lfunction_end - Ltry_end + .long CS2_end + +// Second landing pad (none). + .long 0 + .byte 0 // No action. + +// Action table. + .byte 1 // Action record 1. + .byte 0 // No further action to take. + .long 0 // No type filter for this catch(){} clause. + .align 2
diff --git a/mac/call_with_eh_frame_unittest.mm b/mac/call_with_eh_frame_unittest.mm new file mode 100644 index 0000000..663dae7 --- /dev/null +++ b/mac/call_with_eh_frame_unittest.mm
@@ -0,0 +1,51 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/mac/call_with_eh_frame.h" + +#import <Foundation/Foundation.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace mac { +namespace { + +class CallWithEHFrameTest : public testing::Test { + protected: + void ThrowException() { [NSArray arrayWithObject:nil]; } +}; + +// Catching from within the EHFrame is allowed. +TEST_F(CallWithEHFrameTest, CatchExceptionHigher) { + bool __block saw_exception = false; + base::mac::CallWithEHFrame(^{ + @try { + ThrowException(); + } @catch (NSException* exception) { + saw_exception = true; + } + }); + EXPECT_TRUE(saw_exception); +} + +// Trying to catch an exception outside the EHFrame is blocked. +TEST_F(CallWithEHFrameTest, CatchExceptionLower) { + auto catch_exception_lower = ^{ + bool saw_exception = false; + @try { + base::mac::CallWithEHFrame(^{ + ThrowException(); + }); + } @catch (NSException* exception) { + saw_exception = true; + } + ASSERT_FALSE(saw_exception); + }; + EXPECT_DEATH(catch_exception_lower(), ""); +} + +} // namespace +} // namespace mac +} // namespace base
diff --git a/mac/mac_util.h b/mac/mac_util.h index f8ffa97..af52667 100644 --- a/mac/mac_util.h +++ b/mac/mac_util.h
@@ -147,6 +147,10 @@ BASE_EXPORT bool IsOSYosemite(); BASE_EXPORT bool IsOSYosemiteOrLater(); +// El Capitan is Mac OS X 10.11, Darwin 15. +BASE_EXPORT bool IsOSElCapitan(); +BASE_EXPORT bool IsOSElCapitanOrLater(); + // This should be infrequently used. It only makes sense to use this to avoid // codepaths that are very likely to break on future (unreleased, untested, // unborn) OS releases, or to log when the OS is newer than any known version. @@ -157,6 +161,9 @@ inline bool IsOSLionOrEarlier() { return !IsOSMountainLionOrLater(); } inline bool IsOSMountainLionOrEarlier() { return !IsOSMavericksOrLater(); } inline bool IsOSMavericksOrEarlier() { return !IsOSYosemiteOrLater(); } +inline bool IsOSYosemiteOrEarlier() { + return !IsOSElCapitanOrLater(); +} // When the deployment target is set, the code produced cannot run on earlier // OS releases. That enables some of the IsOS* family to be implemented as @@ -213,6 +220,22 @@ inline bool IsOSLaterThanYosemite_DontCallThis() { return true; } #endif +#if defined(MAC_OS_X_VERSION_10_11) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 +#define BASE_MAC_MAC_UTIL_H_INLINED_GE_10_11 +inline bool IsOSElCapitanOrLater() { + return true; +} +#endif + +#if defined(MAC_OS_X_VERSION_10_11) && \ + MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_11 +#define BASE_MAC_MAC_UTIL_H_INLINED_GT_10_11 +inline bool IsOSElCapitan() { + return false; +} +#endif + // Retrieve the system's model identifier string from the IOKit registry: // for example, "MacPro4,1", "MacBookPro6,1". Returns empty string upon // failure.
diff --git a/mac/mac_util.mm b/mac/mac_util.mm index bdf45de..e1e15dc 100644 --- a/mac/mac_util.mm +++ b/mac/mac_util.mm
@@ -483,6 +483,7 @@ MOUNTAIN_LION_MINOR_VERSION = 8, MAVERICKS_MINOR_VERSION = 9, YOSEMITE_MINOR_VERSION = 10, + EL_CAPITAN_MINOR_VERSION = 11, }; } // namespace @@ -547,6 +548,18 @@ } #endif +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_11) +bool IsOSElCapitan() { + return MacOSXMinorVersion() == EL_CAPITAN_MINOR_VERSION; +} +#endif + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_11) +bool IsOSElCapitanOrLater() { + return MacOSXMinorVersion() >= EL_CAPITAN_MINOR_VERSION; +} +#endif + std::string GetModelIdentifier() { std::string return_string; ScopedIOObject<io_service_t> platform_expert(
diff --git a/macros.h b/macros.h index 0325e74..53b3926 100644 --- a/macros.h +++ b/macros.h
@@ -41,7 +41,7 @@ // that wants to prevent anyone from instantiating it. This is // especially useful for classes containing only static methods. #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName(); \ + TypeName() = delete; \ DISALLOW_COPY_AND_ASSIGN(TypeName) // The arraysize(arr) macro returns the # of elements in an array arr.
diff --git a/memory/BUILD.gn b/memory/BUILD.gn index c53cc05..1f1d3f2 100644 --- a/memory/BUILD.gn +++ b/memory/BUILD.gn
@@ -35,6 +35,9 @@ "scoped_vector.h", "shared_memory.h", "shared_memory_android.cc", + "shared_memory_handle.h", + "shared_memory_handle_mac.cc", + "shared_memory_mac.cc", "shared_memory_nacl.cc", "shared_memory_posix.cc", "shared_memory_win.cc", @@ -54,6 +57,10 @@ sources -= [ "shared_memory_nacl.cc" ] } + if (is_mac) { + sources -= [ "shared_memory_posix.cc" ] + } + if (is_android) { deps = [ "//third_party/ashmem",
diff --git a/memory/ref_counted_delete_on_message_loop.h b/memory/ref_counted_delete_on_message_loop.h index 6a109e8..d278a44 100644 --- a/memory/ref_counted_delete_on_message_loop.h +++ b/memory/ref_counted_delete_on_message_loop.h
@@ -8,8 +8,6 @@ #include "base/location.h" #include "base/logging.h" #include "base/memory/ref_counted.h" -// TODO(ricea): Remove the following include once all callers have been fixed. -#include "base/message_loop/message_loop_proxy.h" #include "base/single_thread_task_runner.h" namespace base {
diff --git a/memory/scoped_vector.h b/memory/scoped_vector.h index 173ea5a..e1e5c72 100644 --- a/memory/scoped_vector.h +++ b/memory/scoped_vector.h
@@ -106,6 +106,10 @@ return v_.insert(position, x); } + iterator insert(iterator position, scoped_ptr<T> x) { + return v_.insert(position, x.release()); + } + // Lets the ScopedVector take ownership of elements in [first,last). template<typename InputIterator> void insert(iterator position, InputIterator first, InputIterator last) {
diff --git a/memory/shared_memory.h b/memory/shared_memory.h index 008bb01..4326758 100644 --- a/memory/shared_memory.h +++ b/memory/shared_memory.h
@@ -17,6 +17,7 @@ #include "base/base_export.h" #include "base/basictypes.h" +#include "base/memory/shared_memory_handle.h" #include "base/process/process_handle.h" #if defined(OS_POSIX) @@ -29,14 +30,6 @@ class FilePath; -// SharedMemoryHandle is a platform specific type which represents -// the underlying OS handle to a shared memory segment. -#if defined(OS_WIN) -typedef HANDLE SharedMemoryHandle; -#elif defined(OS_POSIX) -typedef FileDescriptor SharedMemoryHandle; -#endif - // Options for creating a shared memory object. struct SharedMemoryCreateOptions { SharedMemoryCreateOptions() @@ -89,12 +82,13 @@ // only affects how the SharedMemory will be mmapped. Use // ShareReadOnlyToProcess to drop permissions. TODO(jln,jyasskin): DCHECK // that |read_only| matches the permissions of the handle. - SharedMemory(SharedMemoryHandle handle, bool read_only); + SharedMemory(const 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, + SharedMemory(const SharedMemoryHandle& handle, + bool read_only, ProcessHandle process); // Closes any open files. @@ -257,27 +251,11 @@ return ShareToProcessCommon(process, new_handle, true, SHARE_CURRENT_MODE); } - // DEPRECATED (crbug.com/345734): - // Locks the shared memory. - // - // WARNING: on POSIX the memory locking primitive only works across - // processes, not across threads. The LockDeprecated method is not currently - // used in inner loops, so we protect against multiple threads in a - // critical section using a class global lock. - void LockDeprecated(); - - // DEPRECATED (crbug.com/345734): - // Releases the shared memory lock. - void UnlockDeprecated(); - private: -#if defined(OS_POSIX) && !defined(OS_NACL) -#if !defined(OS_ANDROID) +#if defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_ANDROID) bool PrepareMapFile(ScopedFILE fp, ScopedFD readonly); bool FilePathForMemoryName(const std::string& mem_name, FilePath* path); -#endif - void LockOrUnlockCommon(int function); -#endif // defined(OS_POSIX) && !defined(OS_NACL) +#endif // defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_ANDROID) enum ShareMode { SHARE_READONLY, SHARE_CURRENT_MODE, @@ -298,32 +276,9 @@ void* memory_; bool read_only_; size_t requested_size_; -#if !defined(OS_POSIX) - HANDLE lock_; -#endif DISALLOW_COPY_AND_ASSIGN(SharedMemory); }; - -// DEPRECATED (crbug.com/345734): -// A helper class that acquires the shared memory lock while -// the SharedMemoryAutoLockDeprecated is in scope. -class SharedMemoryAutoLockDeprecated { - public: - explicit SharedMemoryAutoLockDeprecated(SharedMemory* shared_memory) - : shared_memory_(shared_memory) { - shared_memory_->LockDeprecated(); - } - - ~SharedMemoryAutoLockDeprecated() { - shared_memory_->UnlockDeprecated(); - } - - private: - SharedMemory* shared_memory_; - DISALLOW_COPY_AND_ASSIGN(SharedMemoryAutoLockDeprecated); -}; - } // namespace base #endif // BASE_MEMORY_SHARED_MEMORY_H_
diff --git a/memory/shared_memory_handle.h b/memory/shared_memory_handle.h new file mode 100644 index 0000000..7af8729 --- /dev/null +++ b/memory/shared_memory_handle.h
@@ -0,0 +1,97 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_SHARED_MEMORY_HANDLE_H_ +#define BASE_MEMORY_SHARED_MEMORY_HANDLE_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include <sys/types.h> +#include "base/base_export.h" +#include "base/file_descriptor_posix.h" +#include "base/macros.h" +#elif defined(OS_POSIX) +#include <sys/types.h> +#include "base/file_descriptor_posix.h" +#endif + +namespace base { + +class Pickle; + +// SharedMemoryHandle is a platform specific type which represents +// the underlying OS handle to a shared memory segment. +#if defined(OS_WIN) +typedef HANDLE SharedMemoryHandle; +#elif defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS)) +typedef FileDescriptor SharedMemoryHandle; +#else +class BASE_EXPORT SharedMemoryHandle { + public: + enum Type { + // Indicates that the SharedMemoryHandle is backed by a POSIX fd. + POSIX, + // Indicates that the SharedMemoryHandle is backed by the Mach primitive + // "memory object". + MACH, + }; + + // The format that should be used to transmit |Type| over the wire. + typedef int TypeWireFormat; + + // The default constructor returns an invalid SharedMemoryHandle. + SharedMemoryHandle(); + + // Constructs a SharedMemoryHandle backed by the components of a + // FileDescriptor. The newly created instance has the same ownership semantics + // as base::FileDescriptor. This typically means that the SharedMemoryHandle + // takes ownership of the |fd| if |auto_close| is true. Unfortunately, it's + // common for existing code to make shallow copies of SharedMemoryHandle, and + // the one that is finally passed into a base::SharedMemory is the one that + // "consumes" the fd. + explicit SharedMemoryHandle(const base::FileDescriptor& file_descriptor); + SharedMemoryHandle(int fd, bool auto_close); + + // Standard copy constructor. The new instance shares the underlying OS + // primitives. + SharedMemoryHandle(const SharedMemoryHandle& handle); + + // Standard assignment operator. The updated instance shares the underlying + // OS primitives. + SharedMemoryHandle& operator=(const SharedMemoryHandle& handle); + + // Duplicates the underlying OS resources. + SharedMemoryHandle Duplicate() const; + + // Comparison operators. + bool operator==(const SharedMemoryHandle& handle) const; + bool operator!=(const SharedMemoryHandle& handle) const; + + // Returns the type. + Type GetType() const; + + // Whether the underlying OS primitive is valid. + bool IsValid() const; + + // Sets the POSIX fd backing the SharedMemoryHandle. Requires that the + // SharedMemoryHandle be backed by a POSIX fd. + void SetFileHandle(int fd, bool auto_close); + + // This method assumes that the SharedMemoryHandle is backed by a POSIX fd. + // This is eventually no longer going to be true, so please avoid adding new + // uses of this method. + const FileDescriptor GetFileDescriptor() const; + + private: + Type type_; + FileDescriptor file_descriptor_; +}; +#endif + +} // namespace base + +#endif // BASE_MEMORY_SHARED_MEMORY_HANDLE_H_
diff --git a/memory/shared_memory_handle_mac.cc b/memory/shared_memory_handle_mac.cc new file mode 100644 index 0000000..86f5c54 --- /dev/null +++ b/memory/shared_memory_handle_mac.cc
@@ -0,0 +1,86 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory_handle.h" + +#include <unistd.h> + +#include "base/posix/eintr_wrapper.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +namespace base { + +static_assert(sizeof(SharedMemoryHandle::Type) <= + sizeof(SharedMemoryHandle::TypeWireFormat), + "Size of enum SharedMemoryHandle::Type exceeds size of type " + "transmitted over wire."); + +SharedMemoryHandle::SharedMemoryHandle() : type_(POSIX), file_descriptor_() {} + +SharedMemoryHandle::SharedMemoryHandle( + const base::FileDescriptor& file_descriptor) + : type_(POSIX), file_descriptor_(file_descriptor) {} + +SharedMemoryHandle::SharedMemoryHandle(int fd, bool auto_close) + : type_(POSIX), file_descriptor_(fd, auto_close) {} + +SharedMemoryHandle::SharedMemoryHandle(const SharedMemoryHandle& handle) + : type_(handle.type_), file_descriptor_(handle.file_descriptor_) {} + +SharedMemoryHandle& SharedMemoryHandle::operator=( + const SharedMemoryHandle& handle) { + if (this == &handle) + return *this; + + type_ = handle.type_; + file_descriptor_ = handle.file_descriptor_; + return *this; +} + +bool SharedMemoryHandle::operator==(const SharedMemoryHandle& handle) const { + // Invalid handles are always equal, even if they have different types. + if (!IsValid() && !handle.IsValid()) + return true; + + return type_ == handle.type_ && file_descriptor_ == handle.file_descriptor_; +} + +bool SharedMemoryHandle::operator!=(const SharedMemoryHandle& handle) const { + return !(*this == handle); +} + +SharedMemoryHandle::Type SharedMemoryHandle::GetType() const { + return type_; +} + +bool SharedMemoryHandle::IsValid() const { + switch (type_) { + case POSIX: + return file_descriptor_.fd >= 0; + case MACH: + return false; + } +} + +void SharedMemoryHandle::SetFileHandle(int fd, bool auto_close) { + DCHECK_EQ(type_, POSIX); + file_descriptor_.fd = fd; + file_descriptor_.auto_close = auto_close; +} + +const FileDescriptor SharedMemoryHandle::GetFileDescriptor() const { + DCHECK_EQ(type_, POSIX); + return file_descriptor_; +} + +SharedMemoryHandle SharedMemoryHandle::Duplicate() const { + DCHECK_EQ(type_, POSIX); + int duped_handle = HANDLE_EINTR(dup(file_descriptor_.fd)); + if (duped_handle < 0) + return SharedMemoryHandle(); + return SharedMemoryHandle(duped_handle, true); +} + +} // namespace base +#endif // defined(OS_MACOSX) && !defined(OS_IOS)
diff --git a/memory/shared_memory_mac.cc b/memory/shared_memory_mac.cc new file mode 100644 index 0000000..8d8a164 --- /dev/null +++ b/memory/shared_memory_mac.cc
@@ -0,0 +1,460 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory.h" + +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/safe_strerror.h" +#include "base/process/process_metrics.h" +#include "base/profiler/scoped_tracker.h" +#include "base/scoped_generic.h" +#include "base/strings/utf_string_conversions.h" + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.h" +#endif // OS_MACOSX + +namespace base { + +namespace { + +struct ScopedPathUnlinkerTraits { + static FilePath* InvalidValue() { return nullptr; } + + static void Free(FilePath* path) { + // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466437 + // is fixed. + tracked_objects::ScopedTracker tracking_profile( + FROM_HERE_WITH_EXPLICIT_FUNCTION( + "466437 SharedMemory::Create::Unlink")); + if (unlink(path->value().c_str())) + PLOG(WARNING) << "unlink"; + } +}; + +// Unlinks the FilePath when the object is destroyed. +typedef ScopedGeneric<FilePath*, ScopedPathUnlinkerTraits> ScopedPathUnlinker; + +// Makes a temporary file, fdopens it, and then unlinks it. |fp| is populated +// with the fdopened FILE. |readonly_fd| is populated with the opened fd if +// options.share_read_only is true. |path| is populated with the location of +// the file before it was unlinked. +// Returns false if there's an unhandled failure. +bool CreateAnonymousSharedMemory(const SharedMemoryCreateOptions& options, + ScopedFILE* fp, + ScopedFD* readonly_fd, + FilePath* path) { + // It doesn't make sense to have a open-existing private piece of shmem + DCHECK(!options.open_existing_deprecated); + // Q: Why not use the shm_open() etc. APIs? + // A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU + FilePath directory; + ScopedPathUnlinker path_unlinker; + if (GetShmemTempDir(options.executable, &directory)) { + // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466437 + // is fixed. + tracked_objects::ScopedTracker tracking_profile( + FROM_HERE_WITH_EXPLICIT_FUNCTION( + "466437 SharedMemory::Create::OpenTemporaryFile")); + fp->reset(base::CreateAndOpenTemporaryFileInDir(directory, path)); + + // Deleting the file prevents anyone else from mapping it in (making it + // private), and prevents the need for cleanup (once the last fd is + // closed, it is truly freed). + if (*fp) + path_unlinker.reset(path); + } + + if (*fp) { + if (options.share_read_only) { + // TODO(erikchen): Remove ScopedTracker below once + // http://crbug.com/466437 is fixed. + tracked_objects::ScopedTracker tracking_profile( + FROM_HERE_WITH_EXPLICIT_FUNCTION( + "466437 SharedMemory::Create::OpenReadonly")); + // Also open as readonly so that we can ShareReadOnlyToProcess. + readonly_fd->reset(HANDLE_EINTR(open(path->value().c_str(), O_RDONLY))); + if (!readonly_fd->is_valid()) { + DPLOG(ERROR) << "open(\"" << path->value() << "\", O_RDONLY) failed"; + fp->reset(); + return false; + } + } + } + return true; +} +} + +SharedMemory::SharedMemory() + : mapped_file_(-1), + readonly_mapped_file_(-1), + mapped_size_(0), + memory_(NULL), + read_only_(false), + requested_size_(0) {} + +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, bool read_only) + : mapped_file_(GetFdFromSharedMemoryHandle(handle)), + readonly_mapped_file_(-1), + mapped_size_(0), + memory_(NULL), + read_only_(read_only), + requested_size_(0) {} + +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, + bool read_only, + ProcessHandle process) + : mapped_file_(GetFdFromSharedMemoryHandle(handle)), + readonly_mapped_file_(-1), + mapped_size_(0), + memory_(NULL), + read_only_(read_only), + requested_size_(0) { + // We don't handle this case yet (note the ignored parameter); let's die if + // someone comes calling. + NOTREACHED(); +} + +SharedMemory::~SharedMemory() { + Unmap(); + Close(); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle.IsValid(); +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return SharedMemoryHandle(); +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + DCHECK_GE(GetFdFromSharedMemoryHandle(handle), 0); + if (close(GetFdFromSharedMemoryHandle(handle)) < 0) + DPLOG(ERROR) << "close"; +} + +// static +size_t SharedMemory::GetHandleLimit() { + return base::GetMaxFds(); +} + +// static +SharedMemoryHandle SharedMemory::DuplicateHandle( + const SharedMemoryHandle& handle) { + return handle.Duplicate(); +} + +// static +int SharedMemory::GetFdFromSharedMemoryHandle( + const SharedMemoryHandle& handle) { + return handle.GetFileDescriptor().fd; +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + return CreateAnonymous(size) && Map(size); +} + +// static +int SharedMemory::GetSizeFromSharedMemoryHandle( + const SharedMemoryHandle& handle) { + struct stat st; + if (fstat(GetFdFromSharedMemoryHandle(handle), &st) != 0) + return -1; + return st.st_size; +} + +// Chromium mostly only uses the unique/private shmem as specified by +// "name == L"". The exception is in the StatsTable. +// TODO(jrg): there is no way to "clean up" all unused named shmem if +// we restart from a crash. (That isn't a new problem, but it is a problem.) +// In case we want to delete it later, it may be useful to save the value +// of mem_filename after FilePathForMemoryName(). +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466437 + // is fixed. + tracked_objects::ScopedTracker tracking_profile1( + FROM_HERE_WITH_EXPLICIT_FUNCTION("466437 SharedMemory::Create::Start")); + DCHECK_EQ(-1, mapped_file_); + if (options.size == 0) + return false; + + if (options.size > static_cast<size_t>(std::numeric_limits<int>::max())) + return false; + + // This function theoretically can block on the disk, but realistically + // the temporary files we create will just go into the buffer cache + // and be deleted before they ever make it out to disk. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + ScopedFILE fp; + bool fix_size = true; + ScopedFD readonly_fd; + + FilePath path; + if (options.name_deprecated == NULL || options.name_deprecated->empty()) { + bool result = + CreateAnonymousSharedMemory(options, &fp, &readonly_fd, &path); + if (!result) + return false; + } else { + if (!FilePathForMemoryName(*options.name_deprecated, &path)) + return false; + + // Make sure that the file is opened without any permission + // to other users on the system. + const mode_t kOwnerOnly = S_IRUSR | S_IWUSR; + + // First, try to create the file. + int fd = HANDLE_EINTR( + open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly)); + if (fd == -1 && options.open_existing_deprecated) { + // If this doesn't work, try and open an existing file in append mode. + // Opening an existing file in a world writable directory has two main + // security implications: + // - Attackers could plant a file under their control, so ownership of + // the file is checked below. + // - Attackers could plant a symbolic link so that an unexpected file + // is opened, so O_NOFOLLOW is passed to open(). + fd = HANDLE_EINTR( + open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW)); + + // Check that the current user owns the file. + // If uid != euid, then a more complex permission model is used and this + // API is not appropriate. + const uid_t real_uid = getuid(); + const uid_t effective_uid = geteuid(); + struct stat sb; + if (fd >= 0 && (fstat(fd, &sb) != 0 || sb.st_uid != real_uid || + sb.st_uid != effective_uid)) { + LOG(ERROR) << "Invalid owner when opening existing shared memory file."; + close(fd); + return false; + } + + // An existing file was opened, so its size should not be fixed. + fix_size = false; + } + + if (options.share_read_only) { + // Also open as readonly so that we can ShareReadOnlyToProcess. + readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY))); + if (!readonly_fd.is_valid()) { + DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed"; + close(fd); + fd = -1; + return false; + } + } + if (fd >= 0) { + // "a+" is always appropriate: if it's a new file, a+ is similar to w+. + fp.reset(fdopen(fd, "a+")); + } + } + if (fp && fix_size) { + // Get current size. + struct stat stat; + if (fstat(fileno(fp.get()), &stat) != 0) + return false; + const size_t current_size = stat.st_size; + if (current_size != options.size) { + if (HANDLE_EINTR(ftruncate(fileno(fp.get()), options.size)) != 0) + return false; + } + requested_size_ = options.size; + } + if (fp == NULL) { + PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; + return false; + } + + return PrepareMapFile(fp.Pass(), readonly_fd.Pass()); +} + +// Our current implementation of shmem is with mmap()ing of files. +// These files need to be deleted explicitly. +// In practice this call is only needed for unit tests. +bool SharedMemory::Delete(const std::string& name) { + FilePath path; + if (!FilePathForMemoryName(name, &path)) + return false; + + if (PathExists(path)) + return base::DeleteFile(path, false); + + // Doesn't exist, so success. + return true; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + FilePath path; + if (!FilePathForMemoryName(name, &path)) + return false; + + read_only_ = read_only; + + const char* mode = read_only ? "r" : "r+"; + ScopedFILE fp(base::OpenFile(path, mode)); + ScopedFD readonly_fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY))); + if (!readonly_fd.is_valid()) { + DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed"; + return false; + } + return PrepareMapFile(fp.Pass(), readonly_fd.Pass()); +} + +bool SharedMemory::MapAt(off_t offset, size_t bytes) { + if (mapped_file_ == -1) + return false; + + if (bytes > static_cast<size_t>(std::numeric_limits<int>::max())) + return false; + + if (memory_) + return false; + + memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE), + MAP_SHARED, mapped_file_, offset); + + bool mmap_succeeded = memory_ != (void*)-1 && memory_ != NULL; + if (mmap_succeeded) { + mapped_size_ = bytes; + DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + } else { + memory_ = NULL; + } + + return mmap_succeeded; +} + +bool SharedMemory::Unmap() { + if (memory_ == NULL) + return false; + + munmap(memory_, mapped_size_); + memory_ = NULL; + mapped_size_ = 0; + return true; +} + +SharedMemoryHandle SharedMemory::handle() const { + return SharedMemoryHandle(mapped_file_, false); +} + +void SharedMemory::Close() { + if (mapped_file_ > 0) { + if (close(mapped_file_) < 0) + PLOG(ERROR) << "close"; + mapped_file_ = -1; + } + if (readonly_mapped_file_ > 0) { + if (close(readonly_mapped_file_) < 0) + PLOG(ERROR) << "close"; + readonly_mapped_file_ = -1; + } +} + +bool SharedMemory::PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd) { + DCHECK_EQ(-1, mapped_file_); + DCHECK_EQ(-1, readonly_mapped_file_); + if (fp == NULL) + return false; + + // This function theoretically can block on the disk, but realistically + // the temporary files we create will just go into the buffer cache + // and be deleted before they ever make it out to disk. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + struct stat st = {}; + if (fstat(fileno(fp.get()), &st)) + NOTREACHED(); + if (readonly_fd.is_valid()) { + struct stat readonly_st = {}; + if (fstat(readonly_fd.get(), &readonly_st)) + NOTREACHED(); + if (st.st_dev != readonly_st.st_dev || st.st_ino != readonly_st.st_ino) { + LOG(ERROR) << "writable and read-only inodes don't match; bailing"; + return false; + } + } + + mapped_file_ = HANDLE_EINTR(dup(fileno(fp.get()))); + if (mapped_file_ == -1) { + if (errno == EMFILE) { + LOG(WARNING) << "Shared memory creation failed; out of file descriptors"; + return false; + } else { + NOTREACHED() << "Call to dup failed, errno=" << errno; + } + } + readonly_mapped_file_ = readonly_fd.release(); + + return true; +} + +// For the given shmem named |mem_name|, return a filename to mmap() +// (and possibly create). Modifies |filename|. Return false on +// error, or true of we are happy. +bool SharedMemory::FilePathForMemoryName(const std::string& mem_name, + FilePath* path) { + // mem_name will be used for a filename; make sure it doesn't + // contain anything which will confuse us. + DCHECK_EQ(std::string::npos, mem_name.find('/')); + DCHECK_EQ(std::string::npos, mem_name.find('\0')); + + FilePath temp_dir; + if (!GetShmemTempDir(false, &temp_dir)) + return false; + + std::string name_base = std::string(base::mac::BaseBundleID()); + *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name); + return true; +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle* new_handle, + bool close_self, + ShareMode share_mode) { + int handle_to_dup = -1; + switch (share_mode) { + case SHARE_CURRENT_MODE: + handle_to_dup = mapped_file_; + break; + case SHARE_READONLY: + // We could imagine re-opening the file from /dev/fd, but that can't make + // it readonly on Mac: https://codereview.chromium.org/27265002/#msg10 + CHECK_GE(readonly_mapped_file_, 0); + handle_to_dup = readonly_mapped_file_; + break; + } + + const int new_fd = HANDLE_EINTR(dup(handle_to_dup)); + if (new_fd < 0) { + DPLOG(ERROR) << "dup() failed."; + return false; + } + + new_handle->SetFileHandle(new_fd, true); + + if (close_self) { + Unmap(); + Close(); + } + + return true; +} + +} // namespace base
diff --git a/memory/shared_memory_nacl.cc b/memory/shared_memory_nacl.cc index 26dd4a3..1e93cca 100644 --- a/memory/shared_memory_nacl.cc +++ b/memory/shared_memory_nacl.cc
@@ -24,15 +24,15 @@ requested_size_(0) { } -SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, bool read_only) : mapped_file_(handle.fd), mapped_size_(0), memory_(NULL), read_only_(read_only), - requested_size_(0) { -} + requested_size_(0) {} -SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, + bool read_only, ProcessHandle process) : mapped_file_(handle.fd), mapped_size_(0), @@ -139,14 +139,6 @@ } } -void SharedMemory::LockDeprecated() { - NOTIMPLEMENTED(); -} - -void SharedMemory::UnlockDeprecated() { - NOTIMPLEMENTED(); -} - bool SharedMemory::ShareToProcessCommon(ProcessHandle process, SharedMemoryHandle *new_handle, bool close_self,
diff --git a/memory/shared_memory_posix.cc b/memory/shared_memory_posix.cc index 35d746e..7bd9ecf 100644 --- a/memory/shared_memory_posix.cc +++ b/memory/shared_memory_posix.cc
@@ -4,16 +4,13 @@ #include "base/memory/shared_memory.h" -#include <errno.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> -#include <sys/types.h> #include <unistd.h> #include "base/files/file_util.h" #include "base/files/scoped_file.h" -#include "base/lazy_instance.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/safe_strerror.h" @@ -21,13 +18,6 @@ #include "base/profiler/scoped_tracker.h" #include "base/scoped_generic.h" #include "base/strings/utf_string_conversions.h" -#include "base/synchronization/lock.h" -#include "base/threading/platform_thread.h" -#include "base/threading/thread_restrictions.h" - -#if defined(OS_MACOSX) -#include "base/mac/foundation_util.h" -#endif // OS_MACOSX #if defined(OS_ANDROID) #include "base/os_compat_android.h" @@ -38,8 +28,6 @@ namespace { -LazyInstance<Lock>::Leaky g_thread_lock_ = LAZY_INSTANCE_INITIALIZER; - struct ScopedPathUnlinkerTraits { static FilePath* InvalidValue() { return nullptr; } @@ -118,16 +106,16 @@ requested_size_(0) { } -SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, bool read_only) : mapped_file_(handle.fd), readonly_mapped_file_(-1), mapped_size_(0), memory_(NULL), read_only_(read_only), - requested_size_(0) { -} + requested_size_(0) {} -SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, + bool read_only, ProcessHandle process) : mapped_file_(handle.fd), readonly_mapped_file_(-1), @@ -298,7 +286,6 @@ requested_size_ = options.size; } if (fp == NULL) { -#if !defined(OS_MACOSX) PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; FilePath dir = path.DirName(); if (access(dir.value().c_str(), W_OK | X_OK) < 0) { @@ -308,9 +295,6 @@ << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix."; } } -#else - PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; -#endif return false; } @@ -414,16 +398,6 @@ } } -void SharedMemory::LockDeprecated() { - g_thread_lock_.Get().Acquire(); - LockOrUnlockCommon(F_LOCK); -} - -void SharedMemory::UnlockDeprecated() { - LockOrUnlockCommon(F_ULOCK); - g_thread_lock_.Get().Release(); -} - #if !defined(OS_ANDROID) bool SharedMemory::PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd) { DCHECK_EQ(-1, mapped_file_); @@ -477,39 +451,16 @@ if (!GetShmemTempDir(false, &temp_dir)) return false; -#if !defined(OS_MACOSX) #if defined(GOOGLE_CHROME_BUILD) std::string name_base = std::string("com.google.Chrome"); #else std::string name_base = std::string("org.chromium.Chromium"); #endif -#else // OS_MACOSX - std::string name_base = std::string(base::mac::BaseBundleID()); -#endif // OS_MACOSX *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name); return true; } #endif // !defined(OS_ANDROID) -void SharedMemory::LockOrUnlockCommon(int function) { - DCHECK_GE(mapped_file_, 0); - while (lockf(mapped_file_, function, 0) < 0) { - if (errno == EINTR) { - continue; - } else if (errno == ENOLCK) { - // temporary kernel resource exaustion - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500)); - continue; - } else { - NOTREACHED() << "lockf() failed." - << " function:" << function - << " fd:" << mapped_file_ - << " errno:" << errno - << " msg:" << base::safe_strerror(errno); - } - } -} - bool SharedMemory::ShareToProcessCommon(ProcessHandle process, SharedMemoryHandle* new_handle, bool close_self,
diff --git a/memory/shared_memory_unittest.cc b/memory/shared_memory_unittest.cc index 6fe5706..c129e18 100644 --- a/memory/shared_memory_unittest.cc +++ b/memory/shared_memory_unittest.cc
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/atomicops.h" #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/memory/shared_memory.h" @@ -33,7 +34,7 @@ #endif static const int kNumThreads = 5; -#if !defined(OS_IOS) // iOS does not allow multiple processes. +#if !defined(OS_IOS) && !defined(OS_ANDROID) static const int kNumTasks = 5; #endif @@ -90,56 +91,6 @@ const char* const MultipleThreadMain::s_test_name_ = "SharedMemoryOpenThreadTest"; -// TODO(port): -// This test requires the ability to pass file descriptors between processes. -// We haven't done that yet in Chrome for POSIX. -#if defined(OS_WIN) -// Each thread will open the shared memory. Each thread will take the memory, -// and keep changing it while trying to lock it, with some small pauses in -// between. Verify that each thread's value in the shared memory is always -// correct. -class MultipleLockThread : public PlatformThread::Delegate { - public: - explicit MultipleLockThread(int id) : id_(id) {} - ~MultipleLockThread() override {} - - // PlatformThread::Delegate interface. - void ThreadMain() override { - const uint32 kDataSize = sizeof(int); - SharedMemoryHandle handle = NULL; - { - SharedMemory memory1; - EXPECT_TRUE(memory1.CreateNamedDeprecated( - "SharedMemoryMultipleLockThreadTest", true, kDataSize)); - EXPECT_TRUE(memory1.ShareToProcess(GetCurrentProcess(), &handle)); - // TODO(paulg): Implement this once we have a posix version of - // SharedMemory::ShareToProcess. - EXPECT_TRUE(true); - } - - SharedMemory memory2(handle, false); - EXPECT_TRUE(memory2.Map(kDataSize)); - volatile int* const ptr = static_cast<int*>(memory2.memory()); - - for (int idx = 0; idx < 20; idx++) { - memory2.LockDeprecated(); - int i = (id_ << 16) + idx; - *ptr = i; - PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); - EXPECT_EQ(*ptr, i); - memory2.UnlockDeprecated(); - } - - memory2.Close(); - } - - private: - int id_; - - DISALLOW_COPY_AND_ASSIGN(MultipleLockThread); -}; -#endif - } // namespace // Android doesn't support SharedMemory::Open/Delete/ @@ -320,34 +271,6 @@ MultipleThreadMain::CleanUp(); } -// TODO(port): this test requires the MultipleLockThread class -// (defined above), which requires the ability to pass file -// descriptors between processes. We haven't done that yet in Chrome -// for POSIX. -#if defined(OS_WIN) -// Create a set of threads to each open a shared memory segment and write to it -// with the lock held. Verify that they are always reading/writing consistent -// data. -TEST(SharedMemoryTest, Lock) { - PlatformThreadHandle thread_handles[kNumThreads]; - MultipleLockThread* thread_delegates[kNumThreads]; - - // Spawn the threads. - for (int index = 0; index < kNumThreads; ++index) { - PlatformThreadHandle pth; - thread_delegates[index] = new MultipleLockThread(index); - EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); - thread_handles[index] = pth; - } - - // Wait for the threads to finish. - for (int index = 0; index < kNumThreads; ++index) { - PlatformThread::Join(thread_handles[index]); - delete thread_delegates[index]; - } -} -#endif - // Allocate private (unique) shared memory with an empty string for a // name. Make sure several of them don't point to the same thing as // we might expect if the names are equal. @@ -437,12 +360,13 @@ // http://crbug.com/320865 (void)handle; #elif defined(OS_POSIX) - EXPECT_EQ(O_RDONLY, fcntl(handle.fd, F_GETFL) & O_ACCMODE) + int handle_fd = SharedMemory::GetFdFromSharedMemoryHandle(handle); + EXPECT_EQ(O_RDONLY, fcntl(handle_fd, F_GETFL) & O_ACCMODE) << "The descriptor itself should be read-only."; errno = 0; - void* writable = mmap( - NULL, contents.size(), PROT_READ | PROT_WRITE, MAP_SHARED, handle.fd, 0); + void* writable = mmap(NULL, contents.size(), PROT_READ | PROT_WRITE, + MAP_SHARED, handle_fd, 0); int mmap_errno = errno; EXPECT_EQ(MAP_FAILED, writable) << "It shouldn't be possible to re-mmap the descriptor writable."; @@ -596,7 +520,8 @@ EXPECT_TRUE(shared_memory.Create(options)); - int shm_fd = shared_memory.handle().fd; + int shm_fd = + SharedMemory::GetFdFromSharedMemoryHandle(shared_memory.handle()); struct stat shm_stat; EXPECT_EQ(0, fstat(shm_fd, &shm_stat)); // Neither the group, nor others should be able to read the shared memory @@ -622,7 +547,8 @@ // Clean-up the backing file name immediately, we don't need it. EXPECT_TRUE(shared_memory.Delete(shared_mem_name)); - int shm_fd = shared_memory.handle().fd; + int shm_fd = + SharedMemory::GetFdFromSharedMemoryHandle(shared_memory.handle()); struct stat shm_stat; EXPECT_EQ(0, fstat(shm_fd, &shm_stat)); // Neither the group, nor others should have been able to open the shared @@ -647,7 +573,9 @@ shared_memory.Close(); } -#if !defined(OS_IOS) // iOS does not allow multiple processes. +// iOS does not allow multiple processes. +// Android ashmem doesn't support named shared memory. +#if !defined(OS_IOS) && !defined(OS_ANDROID) // On POSIX it is especially important we test shmem across processes, // not just across threads. But the test is enabled on all platforms. @@ -664,53 +592,61 @@ #if defined(OS_MACOSX) mac::ScopedNSAutoreleasePool pool; #endif - const uint32 kDataSize = 1024; SharedMemory memory; - bool rv = memory.CreateNamedDeprecated(s_test_name_, true, kDataSize); + bool rv = memory.CreateNamedDeprecated(s_test_name_, true, s_data_size_); EXPECT_TRUE(rv); if (rv != true) errors++; - rv = memory.Map(kDataSize); + rv = memory.Map(s_data_size_); EXPECT_TRUE(rv); if (rv != true) errors++; int *ptr = static_cast<int*>(memory.memory()); - for (int idx = 0; idx < 20; idx++) { - memory.LockDeprecated(); - int i = (1 << 16) + idx; - *ptr = i; - PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); - if (*ptr != i) - errors++; - memory.UnlockDeprecated(); - } - + // This runs concurrently in multiple processes. Writes need to be atomic. + base::subtle::Barrier_AtomicIncrement(ptr, 1); memory.Close(); return errors; } - private: static const char* const s_test_name_; + static const uint32 s_data_size_; }; const char* const SharedMemoryProcessTest::s_test_name_ = "MPMem"; +const uint32 SharedMemoryProcessTest::s_data_size_ = 1024; -TEST_F(SharedMemoryProcessTest, Tasks) { +TEST_F(SharedMemoryProcessTest, SharedMemoryAcrossProcesses) { SharedMemoryProcessTest::CleanUp(); + // Create a shared memory region. Set the first word to 0. + SharedMemory memory; + bool rv = memory.CreateNamedDeprecated(s_test_name_, true, s_data_size_); + ASSERT_TRUE(rv); + rv = memory.Map(s_data_size_); + ASSERT_TRUE(rv); + int* ptr = static_cast<int*>(memory.memory()); + *ptr = 0; + + // Start |kNumTasks| processes, each of which atomically increments the first + // word by 1. Process processes[kNumTasks]; for (int index = 0; index < kNumTasks; ++index) { processes[index] = SpawnChild("SharedMemoryTestMain"); ASSERT_TRUE(processes[index].IsValid()); } + // Check that each process exited correctly. int exit_code = 0; for (int index = 0; index < kNumTasks; ++index) { EXPECT_TRUE(processes[index].WaitForExit(&exit_code)); EXPECT_EQ(0, exit_code); } + // Check that the shared memory region reflects |kNumTasks| increments. + ASSERT_EQ(kNumTasks, *ptr); + + memory.Close(); SharedMemoryProcessTest::CleanUp(); } @@ -718,6 +654,6 @@ return SharedMemoryProcessTest::TaskTestMain(); } -#endif // !OS_IOS +#endif // !defined(OS_IOS) && !defined(OS_ANDROID) } // namespace base
diff --git a/memory/shared_memory_win.cc b/memory/shared_memory_win.cc index eacf0d6..40dcaae 100644 --- a/memory/shared_memory_win.cc +++ b/memory/shared_memory_win.cc
@@ -29,40 +29,34 @@ SharedMemory::SharedMemory() : mapped_file_(NULL), + mapped_size_(0), memory_(NULL), read_only_(false), - mapped_size_(0), - requested_size_(0), - lock_(NULL) { -} + requested_size_(0) {} SharedMemory::SharedMemory(const std::wstring& name) - : mapped_file_(NULL), + : name_(name), + mapped_file_(NULL), + mapped_size_(0), memory_(NULL), read_only_(false), - requested_size_(0), - mapped_size_(0), - lock_(NULL), - name_(name) { -} + requested_size_(0) {} -SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, bool read_only) : mapped_file_(handle), + mapped_size_(0), memory_(NULL), read_only_(read_only), - requested_size_(0), - mapped_size_(0), - lock_(NULL) { -} + requested_size_(0) {} -SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, +SharedMemory::SharedMemory(const SharedMemoryHandle& handle, + bool read_only, ProcessHandle process) : mapped_file_(NULL), + mapped_size_(0), memory_(NULL), read_only_(read_only), - requested_size_(0), - mapped_size_(0), - lock_(NULL) { + requested_size_(0) { ::DuplicateHandle(process, handle, GetCurrentProcess(), &mapped_file_, read_only_ ? FILE_MAP_READ : FILE_MAP_READ | @@ -73,8 +67,6 @@ SharedMemory::~SharedMemory() { Unmap(); Close(); - if (lock_ != NULL) - CloseHandle(lock_); } // static @@ -270,26 +262,6 @@ } } -void SharedMemory::LockDeprecated() { - if (lock_ == NULL) { - std::wstring name = name_; - name.append(L"lock"); - lock_ = CreateMutex(NULL, FALSE, name.c_str()); - if (lock_ == NULL) { - DPLOG(ERROR) << "Could not create mutex."; - NOTREACHED(); - return; // There is nothing good we can do here. - } - } - DWORD result = WaitForSingleObject(lock_, INFINITE); - DCHECK_EQ(result, WAIT_OBJECT_0); -} - -void SharedMemory::UnlockDeprecated() { - DCHECK(lock_ != NULL); - ReleaseMutex(lock_); -} - SharedMemoryHandle SharedMemory::handle() const { return mapped_file_; }
diff --git a/message_loop/message_loop.cc b/message_loop/message_loop.cc index 4222c77..6bd6730 100644 --- a/message_loop/message_loop.cc +++ b/message_loop/message_loop.cc
@@ -19,6 +19,7 @@ #include "base/thread_task_runner_handle.h" #include "base/threading/thread_local.h" #include "base/time/time.h" +#include "base/trace_event/trace_event.h" #include "base/tracked_objects.h" #if defined(OS_MACOSX) @@ -170,7 +171,8 @@ // Tell the incoming queue that we are dying. incoming_task_queue_->WillDestroyCurrentMessageLoop(); incoming_task_queue_ = NULL; - message_loop_proxy_ = NULL; + unbound_task_runner_ = NULL; + task_runner_ = NULL; // OK, now make it so that no one can find us. lazy_tls_ptr.Pointer()->Set(NULL); @@ -257,27 +259,27 @@ void MessageLoop::PostTask( const tracked_objects::Location& from_here, const Closure& task) { - message_loop_proxy_->PostTask(from_here, task); + task_runner_->PostTask(from_here, task); } void MessageLoop::PostDelayedTask( const tracked_objects::Location& from_here, const Closure& task, TimeDelta delay) { - message_loop_proxy_->PostDelayedTask(from_here, task, delay); + task_runner_->PostDelayedTask(from_here, task, delay); } void MessageLoop::PostNonNestableTask( const tracked_objects::Location& from_here, const Closure& task) { - message_loop_proxy_->PostNonNestableTask(from_here, task); + task_runner_->PostNonNestableTask(from_here, task); } void MessageLoop::PostNonNestableDelayedTask( const tracked_objects::Location& from_here, const Closure& task, TimeDelta delay) { - message_loop_proxy_->PostNonNestableDelayedTask(from_here, task, delay); + task_runner_->PostNonNestableDelayedTask(from_here, task, delay); } void MessageLoop::Run() { @@ -367,6 +369,7 @@ //------------------------------------------------------------------------------ +// static scoped_ptr<MessageLoop> MessageLoop::CreateUnbound( Type type, MessagePumpFactoryCallback pump_factory) { return make_scoped_ptr(new MessageLoop(type, pump_factory)); @@ -386,8 +389,9 @@ message_histogram_(NULL), run_loop_(NULL), incoming_task_queue_(new internal::IncomingTaskQueue(this)), - message_loop_proxy_( - new internal::MessageLoopProxyImpl(incoming_task_queue_)) { + unbound_task_runner_( + new internal::MessageLoopTaskRunner(incoming_task_queue_)), + task_runner_(unbound_task_runner_) { // If type is TYPE_CUSTOM non-null pump_factory must be given. DCHECK_EQ(type_ == TYPE_CUSTOM, !pump_factory_.is_null()); } @@ -403,9 +407,26 @@ lazy_tls_ptr.Pointer()->Set(this); incoming_task_queue_->StartScheduling(); - message_loop_proxy_->BindToCurrentThread(); - thread_task_runner_handle_.reset( - new ThreadTaskRunnerHandle(message_loop_proxy_)); + unbound_task_runner_->BindToCurrentThread(); + unbound_task_runner_ = nullptr; + SetThreadTaskRunnerHandle(); +} + +void MessageLoop::SetTaskRunner( + scoped_refptr<SingleThreadTaskRunner> task_runner) { + DCHECK_EQ(this, current()); + DCHECK(task_runner->BelongsToCurrentThread()); + DCHECK(!unbound_task_runner_); + task_runner_ = task_runner.Pass(); + SetThreadTaskRunnerHandle(); +} + +void MessageLoop::SetThreadTaskRunnerHandle() { + DCHECK_EQ(this, current()); + // Clear the previous thread task runner first because only one can exist at + // a time. + thread_task_runner_handle_.reset(); + thread_task_runner_handle_.reset(new ThreadTaskRunnerHandle(task_runner_)); } void MessageLoop::RunHandler() { @@ -453,10 +474,11 @@ HistogramEvent(kTaskRunEvent); + TRACE_TASK_EXECUTION("toplevel", pending_task); + FOR_EACH_OBSERVER(TaskObserver, task_observers_, WillProcessTask(pending_task)); - task_annotator_.RunTask( - "MessageLoop::PostTask", "MessageLoop::RunTask", pending_task); + task_annotator_.RunTask("MessageLoop::PostTask", pending_task); FOR_EACH_OBSERVER(TaskObserver, task_observers_, DidProcessTask(pending_task));
diff --git a/message_loop/message_loop.h b/message_loop/message_loop.h index f2f89d0..fbb8309 100644 --- a/message_loop/message_loop.h +++ b/message_loop/message_loop.h
@@ -16,8 +16,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/incoming_task_queue.h" -#include "base/message_loop/message_loop_proxy.h" -#include "base/message_loop/message_loop_proxy_impl.h" +#include "base/message_loop/message_loop_task_runner.h" #include "base/message_loop/message_pump.h" #include "base/message_loop/timer_slack.h" #include "base/observer_list.h" @@ -296,19 +295,17 @@ } const std::string& thread_name() const { return thread_name_; } - // Gets the message loop proxy associated with this message loop. - // - // NOTE: Deprecated; prefer task_runner() and the TaskRunner interfaces - scoped_refptr<MessageLoopProxy> message_loop_proxy() { - return message_loop_proxy_; + // Gets the TaskRunner associated with this message loop. + const scoped_refptr<SingleThreadTaskRunner>& task_runner() { + return task_runner_; } - // Gets the TaskRunner associated with this message loop. - // TODO(skyostil): Change this to return a const reference to a refptr - // once the internal type matches what is being returned (crbug.com/465354). - scoped_refptr<SingleThreadTaskRunner> task_runner() { - return message_loop_proxy_; - } + // Sets a new TaskRunner for this message loop. The message loop must already + // have been bound to a thread prior to this call, and the task runner must + // belong to that thread. Note that changing the task runner will also affect + // the ThreadTaskRunnerHandle for the target thread. Must be called on the + // thread to which the message loop is bound. + void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner); // Enables or disables the recursive task processing. This happens in the case // of recursive message loops. Some unwanted message loop may occurs when @@ -425,7 +422,7 @@ // thread the message loop runs on, before calling Run(). // Before BindToCurrentThread() is called only Post*Task() functions can // be called on the message loop. - scoped_ptr<MessageLoop> CreateUnbound( + static scoped_ptr<MessageLoop> CreateUnbound( Type type, MessagePumpFactoryCallback pump_factory); @@ -436,6 +433,10 @@ // Configure various members and bind this message loop to the current thread. void BindToCurrentThread(); + // Sets the ThreadTaskRunnerHandle for the current thread to point to the + // task runner for this message loop. + void SetThreadTaskRunnerHandle(); + // Invokes the actual run loop using the message pump. void RunHandler(); @@ -531,8 +532,11 @@ scoped_refptr<internal::IncomingTaskQueue> incoming_task_queue_; - // The message loop proxy associated with this message loop. - scoped_refptr<internal::MessageLoopProxyImpl> message_loop_proxy_; + // A task runner which we haven't bound to a thread yet. + scoped_refptr<internal::MessageLoopTaskRunner> unbound_task_runner_; + + // The task runner associated with this message loop. + scoped_refptr<SingleThreadTaskRunner> task_runner_; scoped_ptr<ThreadTaskRunnerHandle> thread_task_runner_handle_; template <class T, class R> friend class base::subtle::DeleteHelperInternal;
diff --git a/message_loop/message_loop_proxy.cc b/message_loop/message_loop_proxy.cc deleted file mode 100644 index e5f0142..0000000 --- a/message_loop/message_loop_proxy.cc +++ /dev/null
@@ -1,17 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/message_loop/message_loop_proxy.h" - -#include "base/bind.h" - -namespace base { - -MessageLoopProxy::MessageLoopProxy() { -} - -MessageLoopProxy::~MessageLoopProxy() { -} - -} // namespace base
diff --git a/message_loop/message_loop_proxy.h b/message_loop/message_loop_proxy.h deleted file mode 100644 index d5ecc04..0000000 --- a/message_loop/message_loop_proxy.h +++ /dev/null
@@ -1,50 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_H_ -#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_H_ - -#include "base/base_export.h" -#include "base/compiler_specific.h" -#include "base/memory/ref_counted.h" -#include "base/single_thread_task_runner.h" - -// MessageLoopProxy is deprecated. Code should prefer to depend on TaskRunner -// (or the various specializations) for passing task runners around, and should -// use ThreadTaskRunnerHandle::Get() to get the thread's associated task runner. -// -// See http://crbug.com/391045 for more details. -// Example for these changes: -// -// base::MessageLoopProxy::current() -> base::ThreadTaskRunnerHandle::Get() -// scoped_refptr<base::MessageLoopProxy> -> -// scoped_refptr<base::SingleThreadTaskRunner> -// base::MessageLoopProxy -> base::SingleThreadTaskRunner - -namespace base { - -// This class provides a thread-safe refcounted interface to the Post* methods -// of a message loop. This class can outlive the target message loop. -// MessageLoopProxy objects are constructed automatically for all MessageLoops. -// So, to access them, you can use any of the following: -// Thread::message_loop_proxy() -// MessageLoop::current()->message_loop_proxy() -// MessageLoopProxy::current() -// -// TODO(akalin): Now that we have the *TaskRunner interfaces, we can -// merge this with MessageLoopProxyImpl. -class BASE_EXPORT MessageLoopProxy : public SingleThreadTaskRunner { - public: - // Gets the MessageLoopProxy for the current message loop, creating one if - // needed. - static scoped_refptr<MessageLoopProxy> current(); - - protected: - MessageLoopProxy(); - ~MessageLoopProxy() override; -}; - -} // namespace base - -#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_H_
diff --git a/message_loop/message_loop_proxy_impl_unittest.cc b/message_loop/message_loop_proxy_impl_unittest.cc deleted file mode 100644 index fa25371..0000000 --- a/message_loop/message_loop_proxy_impl_unittest.cc +++ /dev/null
@@ -1,129 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/message_loop/message_loop_proxy_impl.h" - -#include "base/bind.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop.h" -#include "base/message_loop/message_loop_proxy.h" -#include "base/threading/thread.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/platform_test.h" - -namespace base { - -class MessageLoopProxyImplTest : public testing::Test { - public: - void Release() const { - AssertOnIOThread(); - Quit(); - } - - void Quit() const { - loop_.PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); - } - - void AssertOnIOThread() const { - ASSERT_TRUE(io_thread_->message_loop_proxy()->BelongsToCurrentThread()); - ASSERT_EQ(io_thread_->message_loop_proxy(), - MessageLoopProxy::current()); - } - - void AssertOnFileThread() const { - ASSERT_TRUE(file_thread_->message_loop_proxy()->BelongsToCurrentThread()); - ASSERT_EQ(file_thread_->message_loop_proxy(), - MessageLoopProxy::current()); - } - - protected: - void SetUp() override { - io_thread_.reset(new Thread("MessageLoopProxyImplTest_IO")); - file_thread_.reset(new Thread("MessageLoopProxyImplTest_File")); - io_thread_->Start(); - file_thread_->Start(); - } - - void TearDown() override { - io_thread_->Stop(); - file_thread_->Stop(); - } - - static void BasicFunction(MessageLoopProxyImplTest* test) { - test->AssertOnFileThread(); - test->Quit(); - } - - static void AssertNotRun() { - FAIL() << "Callback Should not get executed."; - } - - class DeletedOnFile { - public: - explicit DeletedOnFile(MessageLoopProxyImplTest* test) : test_(test) {} - - ~DeletedOnFile() { - test_->AssertOnFileThread(); - test_->Quit(); - } - - private: - MessageLoopProxyImplTest* test_; - }; - - scoped_ptr<Thread> io_thread_; - scoped_ptr<Thread> file_thread_; - - private: - mutable MessageLoop loop_; -}; - -TEST_F(MessageLoopProxyImplTest, Release) { - EXPECT_TRUE(io_thread_->message_loop_proxy()->ReleaseSoon(FROM_HERE, this)); - MessageLoop::current()->Run(); -} - -TEST_F(MessageLoopProxyImplTest, Delete) { - DeletedOnFile* deleted_on_file = new DeletedOnFile(this); - EXPECT_TRUE(file_thread_->message_loop_proxy()->DeleteSoon( - FROM_HERE, deleted_on_file)); - MessageLoop::current()->Run(); -} - -TEST_F(MessageLoopProxyImplTest, PostTask) { - EXPECT_TRUE(file_thread_->message_loop_proxy()->PostTask( - FROM_HERE, Bind(&MessageLoopProxyImplTest::BasicFunction, - Unretained(this)))); - MessageLoop::current()->Run(); -} - -TEST_F(MessageLoopProxyImplTest, PostTaskAfterThreadExits) { - scoped_ptr<Thread> test_thread( - new Thread("MessageLoopProxyImplTest_Dummy")); - test_thread->Start(); - scoped_refptr<MessageLoopProxy> message_loop_proxy = - test_thread->message_loop_proxy(); - test_thread->Stop(); - - bool ret = message_loop_proxy->PostTask( - FROM_HERE, - Bind(&MessageLoopProxyImplTest::AssertNotRun)); - EXPECT_FALSE(ret); -} - -TEST_F(MessageLoopProxyImplTest, PostTaskAfterThreadIsDeleted) { - scoped_refptr<MessageLoopProxy> message_loop_proxy; - { - scoped_ptr<Thread> test_thread( - new Thread("MessageLoopProxyImplTest_Dummy")); - test_thread->Start(); - message_loop_proxy = test_thread->message_loop_proxy(); - } - bool ret = message_loop_proxy->PostTask( - FROM_HERE, - Bind(&MessageLoopProxyImplTest::AssertNotRun)); - EXPECT_FALSE(ret); -} - -} // namespace base
diff --git a/message_loop/message_loop_proxy_unittest.cc b/message_loop/message_loop_proxy_unittest.cc deleted file mode 100644 index 0b0d9f8..0000000 --- a/message_loop/message_loop_proxy_unittest.cc +++ /dev/null
@@ -1,266 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/message_loop/message_loop_proxy.h" - -#include "base/atomic_sequence_num.h" -#include "base/bind.h" -#include "base/debug/leak_annotations.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop.h" -#include "base/synchronization/waitable_event.h" -#include "base/threading/thread.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { - -namespace { - -class MessageLoopProxyTest : public testing::Test { - public: - MessageLoopProxyTest() - : current_loop_(new MessageLoop()), - task_thread_("task_thread"), - thread_sync_(true, false) { - } - - void DeleteCurrentMessageLoop() { - current_loop_.reset(); - } - - protected: - void SetUp() override { - // Use SetUp() instead of the constructor to avoid posting a task to a - // partialy constructed object. - task_thread_.Start(); - - // Allow us to pause the |task_thread_|'s MessageLoop. - task_thread_.message_loop()->PostTask( - FROM_HERE, - Bind(&MessageLoopProxyTest::BlockTaskThreadHelper, Unretained(this))); - } - - void TearDown() override { - // Make sure the |task_thread_| is not blocked, and stop the thread - // fully before destuction because its tasks may still depend on the - // |thread_sync_| event. - thread_sync_.Signal(); - task_thread_.Stop(); - DeleteCurrentMessageLoop(); - } - - // Make LoopRecorder threadsafe so that there is defined behavior even if a - // threading mistake sneaks into the PostTaskAndReplyRelay implementation. - class LoopRecorder : public RefCountedThreadSafe<LoopRecorder> { - public: - LoopRecorder(MessageLoop** run_on, MessageLoop** deleted_on, - int* destruct_order) - : run_on_(run_on), - deleted_on_(deleted_on), - destruct_order_(destruct_order) { - } - - void RecordRun() { - *run_on_ = MessageLoop::current(); - } - - private: - friend class RefCountedThreadSafe<LoopRecorder>; - ~LoopRecorder() { - *deleted_on_ = MessageLoop::current(); - *destruct_order_ = g_order.GetNext(); - } - - MessageLoop** run_on_; - MessageLoop** deleted_on_; - int* destruct_order_; - }; - - static void RecordLoop(scoped_refptr<LoopRecorder> recorder) { - recorder->RecordRun(); - } - - static void RecordLoopAndQuit(scoped_refptr<LoopRecorder> recorder) { - recorder->RecordRun(); - MessageLoop::current()->QuitWhenIdle(); - } - - void UnblockTaskThread() { - thread_sync_.Signal(); - } - - void BlockTaskThreadHelper() { - thread_sync_.Wait(); - } - - static StaticAtomicSequenceNumber g_order; - - scoped_ptr<MessageLoop> current_loop_; - Thread task_thread_; - - private: - base::WaitableEvent thread_sync_; -}; - -StaticAtomicSequenceNumber MessageLoopProxyTest::g_order; - -TEST_F(MessageLoopProxyTest, PostTaskAndReply_Basic) { - MessageLoop* task_run_on = NULL; - MessageLoop* task_deleted_on = NULL; - int task_delete_order = -1; - MessageLoop* reply_run_on = NULL; - MessageLoop* reply_deleted_on = NULL; - int reply_delete_order = -1; - - scoped_refptr<LoopRecorder> task_recoder = - new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); - scoped_refptr<LoopRecorder> reply_recoder = - new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); - - ASSERT_TRUE(task_thread_.message_loop_proxy()->PostTaskAndReply( - FROM_HERE, - Bind(&RecordLoop, task_recoder), - Bind(&RecordLoopAndQuit, reply_recoder))); - - // Die if base::Bind doesn't retain a reference to the recorders. - task_recoder = NULL; - reply_recoder = NULL; - ASSERT_FALSE(task_deleted_on); - ASSERT_FALSE(reply_deleted_on); - - UnblockTaskThread(); - current_loop_->Run(); - - EXPECT_EQ(task_thread_.message_loop(), task_run_on); - EXPECT_EQ(current_loop_.get(), task_deleted_on); - EXPECT_EQ(current_loop_.get(), reply_run_on); - EXPECT_EQ(current_loop_.get(), reply_deleted_on); - EXPECT_LT(task_delete_order, reply_delete_order); -} - -TEST_F(MessageLoopProxyTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) { - MessageLoop* task_run_on = NULL; - MessageLoop* task_deleted_on = NULL; - int task_delete_order = -1; - MessageLoop* reply_run_on = NULL; - MessageLoop* reply_deleted_on = NULL; - int reply_delete_order = -1; - - scoped_refptr<LoopRecorder> task_recoder = - new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); - scoped_refptr<LoopRecorder> reply_recoder = - new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); - - // Grab a MessageLoopProxy to a dead MessageLoop. - scoped_refptr<MessageLoopProxy> task_loop_proxy = - task_thread_.message_loop_proxy(); - UnblockTaskThread(); - task_thread_.Stop(); - - ASSERT_FALSE(task_loop_proxy->PostTaskAndReply( - FROM_HERE, - Bind(&RecordLoop, task_recoder), - Bind(&RecordLoopAndQuit, reply_recoder))); - - // The relay should have properly deleted its resources leaving us as the only - // reference. - EXPECT_EQ(task_delete_order, reply_delete_order); - ASSERT_TRUE(task_recoder->HasOneRef()); - ASSERT_TRUE(reply_recoder->HasOneRef()); - - // Nothing should have run though. - EXPECT_FALSE(task_run_on); - EXPECT_FALSE(reply_run_on); -} - -TEST_F(MessageLoopProxyTest, PostTaskAndReply_SameLoop) { - MessageLoop* task_run_on = NULL; - MessageLoop* task_deleted_on = NULL; - int task_delete_order = -1; - MessageLoop* reply_run_on = NULL; - MessageLoop* reply_deleted_on = NULL; - int reply_delete_order = -1; - - scoped_refptr<LoopRecorder> task_recoder = - new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); - scoped_refptr<LoopRecorder> reply_recoder = - new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); - - // Enqueue the relay. - ASSERT_TRUE(current_loop_->message_loop_proxy()->PostTaskAndReply( - FROM_HERE, - Bind(&RecordLoop, task_recoder), - Bind(&RecordLoopAndQuit, reply_recoder))); - - // Die if base::Bind doesn't retain a reference to the recorders. - task_recoder = NULL; - reply_recoder = NULL; - ASSERT_FALSE(task_deleted_on); - ASSERT_FALSE(reply_deleted_on); - - current_loop_->Run(); - - EXPECT_EQ(current_loop_.get(), task_run_on); - EXPECT_EQ(current_loop_.get(), task_deleted_on); - EXPECT_EQ(current_loop_.get(), reply_run_on); - EXPECT_EQ(current_loop_.get(), reply_deleted_on); - EXPECT_LT(task_delete_order, reply_delete_order); -} - -TEST_F(MessageLoopProxyTest, PostTaskAndReply_DeadReplyLoopDoesNotDelete) { - // Annotate the scope as having memory leaks to suppress heapchecker reports. - ANNOTATE_SCOPED_MEMORY_LEAK; - MessageLoop* task_run_on = NULL; - MessageLoop* task_deleted_on = NULL; - int task_delete_order = -1; - MessageLoop* reply_run_on = NULL; - MessageLoop* reply_deleted_on = NULL; - int reply_delete_order = -1; - - scoped_refptr<LoopRecorder> task_recoder = - new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); - scoped_refptr<LoopRecorder> reply_recoder = - new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); - - // Enqueue the relay. - task_thread_.message_loop_proxy()->PostTaskAndReply( - FROM_HERE, - Bind(&RecordLoop, task_recoder), - Bind(&RecordLoopAndQuit, reply_recoder)); - - // Die if base::Bind doesn't retain a reference to the recorders. - task_recoder = NULL; - reply_recoder = NULL; - ASSERT_FALSE(task_deleted_on); - ASSERT_FALSE(reply_deleted_on); - - UnblockTaskThread(); - - // Mercilessly whack the current loop before |reply| gets to run. - current_loop_.reset(); - - // This should ensure the relay has been run. We need to record the - // MessageLoop pointer before stopping the thread because Thread::Stop() will - // NULL out its own pointer. - MessageLoop* task_loop = task_thread_.message_loop(); - task_thread_.Stop(); - - EXPECT_EQ(task_loop, task_run_on); - ASSERT_FALSE(task_deleted_on); - EXPECT_FALSE(reply_run_on); - ASSERT_FALSE(reply_deleted_on); - EXPECT_EQ(task_delete_order, reply_delete_order); - - // The PostTaskAndReplyRelay is leaked here. Even if we had a reference to - // it, we cannot just delete it because PostTaskAndReplyRelay's destructor - // checks that MessageLoop::current() is the the same as when the - // PostTaskAndReplyRelay object was constructed. However, this loop must have - // aleady been deleted in order to perform this test. See - // http://crbug.com/86301. -} - -} // namespace - -} // namespace base
diff --git a/message_loop/message_loop_proxy_impl.cc b/message_loop/message_loop_task_runner.cc similarity index 62% rename from message_loop/message_loop_proxy_impl.cc rename to message_loop/message_loop_task_runner.cc index 580620d..d553cfe 100644 --- a/message_loop/message_loop_proxy_impl.cc +++ b/message_loop/message_loop_task_runner.cc
@@ -2,29 +2,26 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/message_loop/message_loop_proxy_impl.h" +#include "base/message_loop/message_loop_task_runner.h" #include "base/location.h" #include "base/logging.h" #include "base/message_loop/incoming_task_queue.h" -#include "base/message_loop/message_loop.h" namespace base { namespace internal { -MessageLoopProxyImpl::MessageLoopProxyImpl( +MessageLoopTaskRunner::MessageLoopTaskRunner( scoped_refptr<IncomingTaskQueue> incoming_queue) - : incoming_queue_(incoming_queue), - valid_thread_id_(kInvalidThreadId) { -} + : incoming_queue_(incoming_queue), valid_thread_id_(kInvalidThreadId) {} -void MessageLoopProxyImpl::BindToCurrentThread() { +void MessageLoopTaskRunner::BindToCurrentThread() { AutoLock lock(valid_thread_id_lock_); DCHECK_EQ(kInvalidThreadId, valid_thread_id_); valid_thread_id_ = PlatformThread::CurrentId(); } -bool MessageLoopProxyImpl::PostDelayedTask( +bool MessageLoopTaskRunner::PostDelayedTask( const tracked_objects::Location& from_here, const base::Closure& task, base::TimeDelta delay) { @@ -32,7 +29,7 @@ return incoming_queue_->AddToIncomingQueue(from_here, task, delay, true); } -bool MessageLoopProxyImpl::PostNonNestableDelayedTask( +bool MessageLoopTaskRunner::PostNonNestableDelayedTask( const tracked_objects::Location& from_here, const base::Closure& task, base::TimeDelta delay) { @@ -40,22 +37,13 @@ return incoming_queue_->AddToIncomingQueue(from_here, task, delay, false); } -bool MessageLoopProxyImpl::RunsTasksOnCurrentThread() const { +bool MessageLoopTaskRunner::RunsTasksOnCurrentThread() const { AutoLock lock(valid_thread_id_lock_); return valid_thread_id_ == PlatformThread::CurrentId(); } -MessageLoopProxyImpl::~MessageLoopProxyImpl() { -} +MessageLoopTaskRunner::~MessageLoopTaskRunner() {} } // namespace internal -scoped_refptr<MessageLoopProxy> -MessageLoopProxy::current() { - MessageLoop* cur_loop = MessageLoop::current(); - if (!cur_loop) - return NULL; - return cur_loop->message_loop_proxy(); -} - } // namespace base
diff --git a/message_loop/message_loop_proxy_impl.h b/message_loop/message_loop_task_runner.h similarity index 60% rename from message_loop/message_loop_proxy_impl.h rename to message_loop/message_loop_task_runner.h index fa611c2..dc2947d 100644 --- a/message_loop/message_loop_proxy_impl.h +++ b/message_loop/message_loop_task_runner.h
@@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_IMPL_H_ -#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_IMPL_H_ +#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_TASK_RUNNER_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_TASK_RUNNER_H_ #include "base/base_export.h" #include "base/memory/ref_counted.h" -#include "base/message_loop/message_loop_proxy.h" #include "base/pending_task.h" +#include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" #include "base/threading/platform_thread.h" @@ -17,18 +17,18 @@ class IncomingTaskQueue; -// A stock implementation of MessageLoopProxy that is created and managed by a -// MessageLoop. For now a MessageLoopProxyImpl can only be created as part of a -// MessageLoop. -class BASE_EXPORT MessageLoopProxyImpl : public MessageLoopProxy { +// A stock implementation of SingleThreadTaskRunner that is created and managed +// by a MessageLoop. For now a MessageLoopTaskRunner can only be created as +// part of a MessageLoop. +class BASE_EXPORT MessageLoopTaskRunner : public SingleThreadTaskRunner { public: - explicit MessageLoopProxyImpl( + explicit MessageLoopTaskRunner( scoped_refptr<IncomingTaskQueue> incoming_queue); - // Initialize this message loop proxy on the current thread. + // Initialize this message loop task runner on the current thread. void BindToCurrentThread(); - // MessageLoopProxy implementation + // SingleThreadTaskRunner implementation bool PostDelayedTask(const tracked_objects::Location& from_here, const base::Closure& task, base::TimeDelta delay) override; @@ -38,10 +38,10 @@ bool RunsTasksOnCurrentThread() const override; private: - friend class RefCountedThreadSafe<MessageLoopProxyImpl>; - ~MessageLoopProxyImpl() override; + friend class RefCountedThreadSafe<MessageLoopTaskRunner>; + ~MessageLoopTaskRunner() override; - // THe incoming queue receiving all posted tasks. + // The incoming queue receiving all posted tasks. scoped_refptr<IncomingTaskQueue> incoming_queue_; // ID of the thread |this| was created on. Could be accessed on multiple @@ -49,10 +49,10 @@ PlatformThreadId valid_thread_id_; mutable Lock valid_thread_id_lock_; - DISALLOW_COPY_AND_ASSIGN(MessageLoopProxyImpl); + DISALLOW_COPY_AND_ASSIGN(MessageLoopTaskRunner); }; } // namespace internal } // namespace base -#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_PROXY_IMPL_H_ +#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_TASK_RUNNER_H_
diff --git a/message_loop/message_loop_task_runner_unittest.cc b/message_loop/message_loop_task_runner_unittest.cc new file mode 100644 index 0000000..caf88af --- /dev/null +++ b/message_loop/message_loop_task_runner_unittest.cc
@@ -0,0 +1,358 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_loop_task_runner.h" + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/debug/leak_annotations.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_task_runner.h" +#include "base/synchronization/waitable_event.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace base { + +class MessageLoopTaskRunnerTest : public testing::Test { + public: + MessageLoopTaskRunnerTest() + : current_loop_(new MessageLoop()), + task_thread_("task_thread"), + thread_sync_(true, false) {} + + void DeleteCurrentMessageLoop() { current_loop_.reset(); } + + protected: + void SetUp() override { + // Use SetUp() instead of the constructor to avoid posting a task to a + // partialy constructed object. + task_thread_.Start(); + + // Allow us to pause the |task_thread_|'s MessageLoop. + task_thread_.message_loop()->PostTask( + FROM_HERE, Bind(&MessageLoopTaskRunnerTest::BlockTaskThreadHelper, + Unretained(this))); + } + + void TearDown() override { + // Make sure the |task_thread_| is not blocked, and stop the thread + // fully before destuction because its tasks may still depend on the + // |thread_sync_| event. + thread_sync_.Signal(); + task_thread_.Stop(); + DeleteCurrentMessageLoop(); + } + + // Make LoopRecorder threadsafe so that there is defined behavior even if a + // threading mistake sneaks into the PostTaskAndReplyRelay implementation. + class LoopRecorder : public RefCountedThreadSafe<LoopRecorder> { + public: + LoopRecorder(MessageLoop** run_on, + MessageLoop** deleted_on, + int* destruct_order) + : run_on_(run_on), + deleted_on_(deleted_on), + destruct_order_(destruct_order) {} + + void RecordRun() { *run_on_ = MessageLoop::current(); } + + private: + friend class RefCountedThreadSafe<LoopRecorder>; + ~LoopRecorder() { + *deleted_on_ = MessageLoop::current(); + *destruct_order_ = g_order.GetNext(); + } + + MessageLoop** run_on_; + MessageLoop** deleted_on_; + int* destruct_order_; + }; + + static void RecordLoop(scoped_refptr<LoopRecorder> recorder) { + recorder->RecordRun(); + } + + static void RecordLoopAndQuit(scoped_refptr<LoopRecorder> recorder) { + recorder->RecordRun(); + MessageLoop::current()->QuitWhenIdle(); + } + + void UnblockTaskThread() { thread_sync_.Signal(); } + + void BlockTaskThreadHelper() { thread_sync_.Wait(); } + + static StaticAtomicSequenceNumber g_order; + + scoped_ptr<MessageLoop> current_loop_; + Thread task_thread_; + + private: + base::WaitableEvent thread_sync_; +}; + +StaticAtomicSequenceNumber MessageLoopTaskRunnerTest::g_order; + +TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_Basic) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + ASSERT_TRUE(task_thread_.task_runner()->PostTaskAndReply( + FROM_HERE, Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + current_loop_->Run(); + + EXPECT_EQ(task_thread_.message_loop(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Grab a task runner to a dead MessageLoop. + scoped_refptr<SingleThreadTaskRunner> task_runner = + task_thread_.task_runner(); + UnblockTaskThread(); + task_thread_.Stop(); + + ASSERT_FALSE( + task_runner->PostTaskAndReply(FROM_HERE, Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // The relay should have properly deleted its resources leaving us as the only + // reference. + EXPECT_EQ(task_delete_order, reply_delete_order); + ASSERT_TRUE(task_recoder->HasOneRef()); + ASSERT_TRUE(reply_recoder->HasOneRef()); + + // Nothing should have run though. + EXPECT_FALSE(task_run_on); + EXPECT_FALSE(reply_run_on); +} + +TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_SameLoop) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + ASSERT_TRUE(current_loop_->task_runner()->PostTaskAndReply( + FROM_HERE, Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + current_loop_->Run(); + + EXPECT_EQ(current_loop_.get(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_DeadReplyLoopDoesNotDelete) { + // Annotate the scope as having memory leaks to suppress heapchecker reports. + ANNOTATE_SCOPED_MEMORY_LEAK; + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + task_thread_.task_runner()->PostTaskAndReply( + FROM_HERE, Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder)); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + + // Mercilessly whack the current loop before |reply| gets to run. + current_loop_.reset(); + + // This should ensure the relay has been run. We need to record the + // MessageLoop pointer before stopping the thread because Thread::Stop() will + // NULL out its own pointer. + MessageLoop* task_loop = task_thread_.message_loop(); + task_thread_.Stop(); + + EXPECT_EQ(task_loop, task_run_on); + ASSERT_FALSE(task_deleted_on); + EXPECT_FALSE(reply_run_on); + ASSERT_FALSE(reply_deleted_on); + EXPECT_EQ(task_delete_order, reply_delete_order); + + // The PostTaskAndReplyRelay is leaked here. Even if we had a reference to + // it, we cannot just delete it because PostTaskAndReplyRelay's destructor + // checks that MessageLoop::current() is the the same as when the + // PostTaskAndReplyRelay object was constructed. However, this loop must have + // aleady been deleted in order to perform this test. See + // http://crbug.com/86301. +} + +class MessageLoopTaskRunnerThreadingTest : public testing::Test { + public: + void Release() const { + AssertOnIOThread(); + Quit(); + } + + void Quit() const { + loop_.PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); + } + + void AssertOnIOThread() const { + ASSERT_TRUE(io_thread_->task_runner()->BelongsToCurrentThread()); + ASSERT_EQ(io_thread_->task_runner(), ThreadTaskRunnerHandle::Get()); + } + + void AssertOnFileThread() const { + ASSERT_TRUE(file_thread_->task_runner()->BelongsToCurrentThread()); + ASSERT_EQ(file_thread_->task_runner(), ThreadTaskRunnerHandle::Get()); + } + + protected: + void SetUp() override { + io_thread_.reset(new Thread("MessageLoopTaskRunnerThreadingTest_IO")); + file_thread_.reset(new Thread("MessageLoopTaskRunnerThreadingTest_File")); + io_thread_->Start(); + file_thread_->Start(); + } + + void TearDown() override { + io_thread_->Stop(); + file_thread_->Stop(); + } + + static void BasicFunction(MessageLoopTaskRunnerThreadingTest* test) { + test->AssertOnFileThread(); + test->Quit(); + } + + static void AssertNotRun() { FAIL() << "Callback Should not get executed."; } + + class DeletedOnFile { + public: + explicit DeletedOnFile(MessageLoopTaskRunnerThreadingTest* test) + : test_(test) {} + + ~DeletedOnFile() { + test_->AssertOnFileThread(); + test_->Quit(); + } + + private: + MessageLoopTaskRunnerThreadingTest* test_; + }; + + scoped_ptr<Thread> io_thread_; + scoped_ptr<Thread> file_thread_; + + private: + mutable MessageLoop loop_; +}; + +TEST_F(MessageLoopTaskRunnerThreadingTest, Release) { + EXPECT_TRUE(io_thread_->task_runner()->ReleaseSoon(FROM_HERE, this)); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopTaskRunnerThreadingTest, Delete) { + DeletedOnFile* deleted_on_file = new DeletedOnFile(this); + EXPECT_TRUE( + file_thread_->task_runner()->DeleteSoon(FROM_HERE, deleted_on_file)); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopTaskRunnerThreadingTest, PostTask) { + EXPECT_TRUE(file_thread_->task_runner()->PostTask( + FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::BasicFunction, + Unretained(this)))); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadExits) { + scoped_ptr<Thread> test_thread( + new Thread("MessageLoopTaskRunnerThreadingTest_Dummy")); + test_thread->Start(); + scoped_refptr<SingleThreadTaskRunner> task_runner = + test_thread->task_runner(); + test_thread->Stop(); + + bool ret = task_runner->PostTask( + FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::AssertNotRun)); + EXPECT_FALSE(ret); +} + +TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadIsDeleted) { + scoped_refptr<SingleThreadTaskRunner> task_runner; + { + scoped_ptr<Thread> test_thread( + new Thread("MessageLoopTaskRunnerThreadingTest_Dummy")); + test_thread->Start(); + task_runner = test_thread->task_runner(); + } + bool ret = task_runner->PostTask( + FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::AssertNotRun)); + EXPECT_FALSE(ret); +} + +} // namespace base
diff --git a/message_loop/message_loop_unittest.cc b/message_loop/message_loop_unittest.cc index ddde6bb..9c17017 100644 --- a/message_loop/message_loop_unittest.cc +++ b/message_loop/message_loop_unittest.cc
@@ -10,12 +10,12 @@ #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop.h" -#include "base/message_loop/message_loop_proxy_impl.h" #include "base/message_loop/message_loop_test.h" #include "base/pending_task.h" #include "base/posix/eintr_wrapper.h" #include "base/run_loop.h" #include "base/synchronization/waitable_event.h" +#include "base/test/test_simple_task_runner.h" #include "base/thread_task_runner_handle.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" @@ -1012,4 +1012,26 @@ } #endif // defined(OS_WIN) +TEST(MessageLoopTest, SetTaskRunner) { + MessageLoop loop; + scoped_refptr<SingleThreadTaskRunner> new_runner(new TestSimpleTaskRunner()); + + loop.SetTaskRunner(new_runner); + EXPECT_EQ(new_runner, loop.task_runner()); + EXPECT_EQ(new_runner, ThreadTaskRunnerHandle::Get()); +} + +TEST(MessageLoopTest, OriginalRunnerWorks) { + MessageLoop loop; + scoped_refptr<SingleThreadTaskRunner> new_runner(new TestSimpleTaskRunner()); + scoped_refptr<SingleThreadTaskRunner> original_runner(loop.task_runner()); + loop.SetTaskRunner(new_runner); + + scoped_refptr<Foo> foo(new Foo()); + original_runner->PostTask(FROM_HERE, + Bind(&Foo::Test1ConstRef, foo.get(), "a")); + loop.RunUntilIdle(); + EXPECT_EQ(1, foo->test_count()); +} + } // namespace base
diff --git a/message_loop/message_pump_mac.mm b/message_loop/message_pump_mac.mm index 914977b..53e3363 100644 --- a/message_loop/message_pump_mac.mm +++ b/message_loop/message_pump_mac.mm
@@ -10,6 +10,7 @@ #include <limits> #include "base/logging.h" +#include "base/mac/call_with_eh_frame.h" #include "base/mac/scoped_cftyperef.h" #include "base/message_loop/timer_slack.h" #include "base/run_loop.h" @@ -300,7 +301,9 @@ // static void MessagePumpCFRunLoopBase::RunWorkSource(void* info) { MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); - self->RunWork(); + base::mac::CallWithEHFrame(^{ + self->RunWork(); + }); } // Called by MessagePumpCFRunLoopBase::RunWorkSource. @@ -359,7 +362,9 @@ // static void MessagePumpCFRunLoopBase::RunIdleWorkSource(void* info) { MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); - self->RunIdleWork(); + base::mac::CallWithEHFrame(^{ + self->RunIdleWork(); + }); } // Called by MessagePumpCFRunLoopBase::RunIdleWorkSource. @@ -393,7 +398,9 @@ // static void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) { MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); - self->RunNestingDeferredWork(); + base::mac::CallWithEHFrame(^{ + self->RunNestingDeferredWork(); + }); } // Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource. @@ -440,15 +447,16 @@ CFRunLoopActivity activity, void* info) { MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + base::mac::CallWithEHFrame(^{ + // Attempt to do some idle work before going to sleep. + self->RunIdleWork(); - // Attempt to do some idle work before going to sleep. - self->RunIdleWork(); - - // The run loop is about to go to sleep. If any of the work done since it - // started or woke up resulted in a nested run loop running, - // nesting-deferred work may have accumulated. Schedule it for processing - // if appropriate. - self->MaybeScheduleNestingDeferredWork(); + // The run loop is about to go to sleep. If any of the work done since it + // started or woke up resulted in a nested run loop running, + // nesting-deferred work may have accumulated. Schedule it for processing + // if appropriate. + self->MaybeScheduleNestingDeferredWork(); + }); } // Called from the run loop. @@ -463,7 +471,9 @@ // level did not sleep or exit, nesting-deferred work may have accumulated // if a nested loop ran. Schedule nesting-deferred work for processing if // appropriate. - self->MaybeScheduleNestingDeferredWork(); + base::mac::CallWithEHFrame(^{ + self->MaybeScheduleNestingDeferredWork(); + }); } // Called from the run loop. @@ -498,7 +508,9 @@ // to sleep or exiting. It must be called before decrementing the // value so that the value still corresponds to the level of the exiting // loop. - self->MaybeScheduleNestingDeferredWork(); + base::mac::CallWithEHFrame(^{ + self->MaybeScheduleNestingDeferredWork(); + }); --self->nesting_level_; break; @@ -506,7 +518,9 @@ break; } - self->EnterExitRunLoop(activity); + base::mac::CallWithEHFrame(^{ + self->EnterExitRunLoop(activity); + }); } // Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default
diff --git a/metrics/field_trial_unittest.cc b/metrics/field_trial_unittest.cc index f1a1042..ed84d86 100644 --- a/metrics/field_trial_unittest.cc +++ b/metrics/field_trial_unittest.cc
@@ -1021,9 +1021,9 @@ scoped_refptr<base::FieldTrial> trial( new base::FieldTrial("test", kBucketCount, "default", entropy)); for (int j = 0; j < kBucketCount; ++j) - trial->AppendGroup(base::StringPrintf("%d", j), 1); + trial->AppendGroup(base::IntToString(j), 1); - EXPECT_EQ(base::StringPrintf("%d", i), trial->group_name()); + EXPECT_EQ(base::IntToString(i), trial->group_name()); } }
diff --git a/metrics/histogram.cc b/metrics/histogram.cc index 1550420..0c9fcb7 100644 --- a/metrics/histogram.cc +++ b/metrics/histogram.cc
@@ -17,6 +17,7 @@ #include "base/compiler_specific.h" #include "base/debug/alias.h" #include "base/logging.h" +#include "base/metrics/histogram_macros.h" #include "base/metrics/sample_vector.h" #include "base/metrics/statistics_recorder.h" #include "base/pickle.h" @@ -130,6 +131,23 @@ flags); } +HistogramBase* Histogram::FactoryGet(const char* name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags) { + return FactoryGet(std::string(name), minimum, maximum, bucket_count, flags); +} + +HistogramBase* Histogram::FactoryTimeGet(const char* name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags) { + return FactoryTimeGet(std::string(name), minimum, maximum, bucket_count, + flags); +} + // 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 @@ -262,6 +280,8 @@ if (value < 0) value = 0; samples_->Accumulate(value, 1); + + FindAndRunCallback(value); } scoped_ptr<HistogramSamples> Histogram::SnapshotSamples() const { @@ -531,6 +551,23 @@ flags); } +HistogramBase* LinearHistogram::FactoryGet(const char* name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags) { + return FactoryGet(std::string(name), minimum, maximum, bucket_count, flags); +} + +HistogramBase* LinearHistogram::FactoryTimeGet(const char* name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags) { + return FactoryTimeGet(std::string(name), minimum, maximum, bucket_count, + flags); +} + HistogramBase* LinearHistogram::FactoryGetWithRangeDescription( const std::string& name, Sample minimum, @@ -676,6 +713,10 @@ return histogram; } +HistogramBase* BooleanHistogram::FactoryGet(const char* name, int32 flags) { + return FactoryGet(std::string(name), flags); +} + HistogramType BooleanHistogram::GetHistogramType() const { return BOOLEAN_HISTOGRAM; } @@ -736,6 +777,13 @@ return histogram; } +HistogramBase* CustomHistogram::FactoryGet( + const char* name, + const std::vector<Sample>& custom_ranges, + int32 flags) { + return FactoryGet(std::string(name), custom_ranges, flags); +} + HistogramType CustomHistogram::GetHistogramType() const { return CUSTOM_HISTOGRAM; }
diff --git a/metrics/histogram.h b/metrics/histogram.h index c13f05e..58bc029 100644 --- a/metrics/histogram.h +++ b/metrics/histogram.h
@@ -121,6 +121,20 @@ size_t bucket_count, int32 flags); + // Overloads of the above two functions that take a const char* |name| param, + // to avoid code bloat from the std::string constructor being inlined into + // call sites. + static HistogramBase* FactoryGet(const char* name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags); + static HistogramBase* FactoryTimeGet(const char* name, + base::TimeDelta minimum, + base::TimeDelta maximum, + size_t bucket_count, + int32 flags); + static void InitializeBucketRanges(Sample minimum, Sample maximum, BucketRanges* ranges); @@ -277,6 +291,20 @@ size_t bucket_count, int32 flags); + // Overloads of the above two functions that take a const char* |name| param, + // to avoid code bloat from the std::string constructor being inlined into + // call sites. + static HistogramBase* FactoryGet(const char* name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags); + static HistogramBase* FactoryTimeGet(const char* name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags); + struct DescriptionPair { Sample sample; const char* description; // Null means end of a list of pairs. @@ -339,6 +367,11 @@ public: static HistogramBase* FactoryGet(const std::string& name, int32 flags); + // Overload of the above function that takes a const char* |name| param, + // to avoid code bloat from the std::string constructor being inlined into + // call sites. + static HistogramBase* FactoryGet(const char* name, int32 flags); + HistogramType GetHistogramType() const override; private: @@ -364,6 +397,13 @@ const std::vector<Sample>& custom_ranges, int32 flags); + // Overload of the above function that takes a const char* |name| param, + // to avoid code bloat from the std::string constructor being inlined into + // call sites. + static HistogramBase* FactoryGet(const char* name, + const std::vector<Sample>& custom_ranges, + int32 flags); + // Overridden from Histogram: HistogramType GetHistogramType() const override;
diff --git a/metrics/histogram_base.cc b/metrics/histogram_base.cc index de34c79..6b3f69c 100644 --- a/metrics/histogram_base.cc +++ b/metrics/histogram_base.cc
@@ -12,6 +12,7 @@ #include "base/metrics/histogram.h" #include "base/metrics/histogram_samples.h" #include "base/metrics/sparse_histogram.h" +#include "base/metrics/statistics_recorder.h" #include "base/pickle.h" #include "base/process/process_handle.h" #include "base/strings/stringprintf.h" @@ -117,6 +118,16 @@ serializer.Serialize(root); } +void HistogramBase::FindAndRunCallback(HistogramBase::Sample sample) const { + if ((flags_ & kCallbackExists) == 0) + return; + + StatisticsRecorder::OnSampleCallback cb = + StatisticsRecorder::FindCallback(histogram_name()); + if (!cb.is_null()) + cb.Run(sample); +} + void HistogramBase::WriteAsciiBucketGraph(double current_size, double max_size, std::string* output) const {
diff --git a/metrics/histogram_base.h b/metrics/histogram_base.h index 006395b..1bc1f6e 100644 --- a/metrics/histogram_base.h +++ b/metrics/histogram_base.h
@@ -31,7 +31,7 @@ // processes into the browser. If you create another class that inherits from // HistogramBase, add new histogram types and names below. -enum BASE_EXPORT HistogramType { +enum HistogramType { HISTOGRAM, LINEAR_HISTOGRAM, BOOLEAN_HISTOGRAM, @@ -74,6 +74,12 @@ // the source histogram!). kIPCSerializationSourceFlag = 0x10, + // Indicates that a callback exists for when a new sample is recorded on + // this histogram. We store this as a flag with the histogram since + // histograms can be in performance critical code, and this allows us + // to shortcut looking up the callback if it doesn't exist. + kCallbackExists = 0x20, + // Only for Histogram and its sub classes: fancy bucket-naming support. kHexRangePrintingFlag = 0x8000, }; @@ -92,7 +98,7 @@ explicit HistogramBase(const std::string& name); virtual ~HistogramBase(); - std::string histogram_name() const { return histogram_name_; } + const std::string& histogram_name() const { return histogram_name_; } // Comapres |name| to the histogram name and triggers a DCHECK if they do not // match. This is a helper function used by histogram macros, which results in @@ -172,6 +178,10 @@ double scaled_sum, std::string* output) const; + // Retrieves the callback for this histogram, if one exists, and runs it + // passing |sample| as the parameter. + void FindAndRunCallback(Sample sample) const; + private: const std::string histogram_name_; int32_t flags_;
diff --git a/metrics/histogram_macros.h b/metrics/histogram_macros.h index 2aee1a5..9867df8 100644 --- a/metrics/histogram_macros.h +++ b/metrics/histogram_macros.h
@@ -189,6 +189,9 @@ #define UMA_HISTOGRAM_COUNTS_100(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ name, sample, 1, 100, 50) +#define UMA_HISTOGRAM_COUNTS_1000(name, sample) \ + UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 1000, 50) + #define UMA_HISTOGRAM_COUNTS_10000(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ name, sample, 1, 10000, 50)
diff --git a/metrics/histogram_snapshot_manager_unittest.cc b/metrics/histogram_snapshot_manager_unittest.cc index 3a1fd40..e9e7398 100644 --- a/metrics/histogram_snapshot_manager_unittest.cc +++ b/metrics/histogram_snapshot_manager_unittest.cc
@@ -7,8 +7,8 @@ #include <string> #include <vector> -#include "base/metrics/histogram.h" #include "base/metrics/histogram_delta_serialization.h" +#include "base/metrics/histogram_macros.h" #include "base/metrics/statistics_recorder.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/metrics/histogram_unittest.cc b/metrics/histogram_unittest.cc index 2e3a1ee..f189267 100644 --- a/metrics/histogram_unittest.cc +++ b/metrics/histogram_unittest.cc
@@ -11,6 +11,7 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram_macros.h" #include "base/metrics/sample_vector.h" #include "base/metrics/statistics_recorder.h" #include "base/pickle.h"
diff --git a/metrics/sparse_histogram.cc b/metrics/sparse_histogram.cc index e5cdb43..a853dce 100644 --- a/metrics/sparse_histogram.cc +++ b/metrics/sparse_histogram.cc
@@ -46,8 +46,12 @@ } void SparseHistogram::Add(Sample value) { - base::AutoLock auto_lock(lock_); - samples_.Accumulate(value, 1); + { + base::AutoLock auto_lock(lock_); + samples_.Accumulate(value, 1); + } + + FindAndRunCallback(value); } scoped_ptr<HistogramSamples> SparseHistogram::SnapshotSamples() const {
diff --git a/metrics/statistics_recorder.cc b/metrics/statistics_recorder.cc index 85408e1..87ffa3d 100644 --- a/metrics/statistics_recorder.cc +++ b/metrics/statistics_recorder.cc
@@ -10,6 +10,7 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" +#include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" #include "base/values.h" @@ -57,10 +58,19 @@ histogram_to_return = histogram; } else { const std::string& name = histogram->histogram_name(); - HistogramMap::iterator it = histograms_->find(name); + HistogramMap::iterator it = histograms_->find(HistogramNameRef(name)); if (histograms_->end() == it) { - (*histograms_)[name] = histogram; + (*histograms_)[HistogramNameRef(name)] = histogram; ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322 + // If there are callbacks for this histogram, we set the kCallbackExists + // flag. + auto callback_iterator = callbacks_->find(name); + if (callback_iterator != callbacks_->end()) { + if (!callback_iterator->second.is_null()) + histogram->SetFlags(HistogramBase::kCallbackExists); + else + histogram->ClearFlags(HistogramBase::kCallbackExists); + } histogram_to_return = histogram; } else if (histogram == it->second) { // The histogram was registered before. @@ -190,7 +200,7 @@ return; for (const auto& entry : *histograms_) { - DCHECK_EQ(entry.first, entry.second->histogram_name()); + DCHECK_EQ(entry.first.name_, entry.second->histogram_name()); output->push_back(entry.second); } } @@ -219,12 +229,64 @@ if (histograms_ == NULL) return NULL; - HistogramMap::iterator it = histograms_->find(name); + HistogramMap::iterator it = histograms_->find(HistogramNameRef(name)); if (histograms_->end() == it) return NULL; return it->second; } +// static +bool StatisticsRecorder::SetCallback( + const std::string& name, + const StatisticsRecorder::OnSampleCallback& cb) { + DCHECK(!cb.is_null()); + if (lock_ == NULL) + return false; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return false; + + if (ContainsKey(*callbacks_, name)) + return false; + callbacks_->insert(std::make_pair(name, cb)); + + auto histogram_iterator = histograms_->find(HistogramNameRef(name)); + if (histogram_iterator != histograms_->end()) + histogram_iterator->second->SetFlags(HistogramBase::kCallbackExists); + + return true; +} + +// static +void StatisticsRecorder::ClearCallback(const std::string& name) { + if (lock_ == NULL) + return; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return; + + callbacks_->erase(name); + + // We also clear the flag from the histogram (if it exists). + auto histogram_iterator = histograms_->find(HistogramNameRef(name)); + if (histogram_iterator != histograms_->end()) + histogram_iterator->second->ClearFlags(HistogramBase::kCallbackExists); +} + +// static +StatisticsRecorder::OnSampleCallback StatisticsRecorder::FindCallback( + const std::string& name) { + if (lock_ == NULL) + return OnSampleCallback(); + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return OnSampleCallback(); + + auto callback_iterator = callbacks_->find(name); + return callback_iterator != callbacks_->end() ? callback_iterator->second + : OnSampleCallback(); +} + // private static void StatisticsRecorder::GetSnapshot(const std::string& query, Histograms* snapshot) { @@ -235,7 +297,7 @@ return; for (const auto& entry : *histograms_) { - if (entry.first.find(query) != std::string::npos) + if (entry.first.name_.find(query) != std::string::npos) snapshot->push_back(entry.second); } } @@ -256,6 +318,7 @@ } base::AutoLock auto_lock(*lock_); histograms_ = new HistogramMap; + callbacks_ = new CallbackMap; ranges_ = new RangesMap; if (VLOG_IS_ON(1)) @@ -274,14 +337,17 @@ // Clean up. scoped_ptr<HistogramMap> histograms_deleter; + scoped_ptr<CallbackMap> callbacks_deleter; scoped_ptr<RangesMap> ranges_deleter; // We don't delete lock_ on purpose to avoid having to properly protect // against it going away after we checked for NULL in the static methods. { base::AutoLock auto_lock(*lock_); histograms_deleter.reset(histograms_); + callbacks_deleter.reset(callbacks_); ranges_deleter.reset(ranges_); histograms_ = NULL; + callbacks_ = NULL; ranges_ = NULL; } // We are going to leak the histograms and the ranges. @@ -291,6 +357,8 @@ // static StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL; // static +StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = NULL; +// static StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = NULL; // static base::Lock* StatisticsRecorder::lock_ = NULL;
diff --git a/metrics/statistics_recorder.h b/metrics/statistics_recorder.h index b523057..729f230 100644 --- a/metrics/statistics_recorder.h +++ b/metrics/statistics_recorder.h
@@ -17,13 +17,14 @@ #include "base/base_export.h" #include "base/basictypes.h" +#include "base/callback.h" #include "base/gtest_prod_util.h" #include "base/lazy_instance.h" +#include "base/metrics/histogram_base.h" namespace base { class BucketRanges; -class HistogramBase; class Lock; class BASE_EXPORT StatisticsRecorder { @@ -75,9 +76,47 @@ // histograms). static void GetSnapshot(const std::string& query, Histograms* snapshot); + typedef base::Callback<void(HistogramBase::Sample)> OnSampleCallback; + + // SetCallback sets the callback to notify when a new sample is recorded on + // the histogram referred to by |histogram_name|. The call to this method can + // be be done before or after the histogram is created. This method is thread + // safe. The return value is whether or not the callback was successfully set. + static bool SetCallback(const std::string& histogram_name, + const OnSampleCallback& callback); + + // ClearCallback clears any callback set on the histogram referred to by + // |histogram_name|. This method is thread safe. + static void ClearCallback(const std::string& histogram_name); + + // FindCallback retrieves the callback for the histogram referred to by + // |histogram_name|, or a null callback if no callback exists for this + // histogram. This method is thread safe. + static OnSampleCallback FindCallback(const std::string& histogram_name); + private: + // HistogramNameRef holds a weak const ref to the name field of the associated + // Histogram object, allowing re-use of the underlying string storage for the + // map keys. The wrapper is required as using "const std::string&" as the key + // results in compile errors. + struct HistogramNameRef { + explicit HistogramNameRef(const std::string& name) : name_(name){}; + + // Operator < is necessary to use this type as a std::map key. + bool operator<(const HistogramNameRef& other) const { + return name_ < other.name_; + } + + // Weak, owned by the associated Histogram object. + const std::string& name_; + }; + // We keep all registered histograms in a map, from name to histogram. - typedef std::map<std::string, HistogramBase*> HistogramMap; + typedef std::map<HistogramNameRef, HistogramBase*> HistogramMap; + + // We keep a map of callbacks to histograms, so that as histograms are + // created, we can set the callback properly. + typedef std::map<std::string, OnSampleCallback> CallbackMap; // We keep all |bucket_ranges_| in a map, from checksum to a list of // |bucket_ranges_|. Checksum is calculated from the |ranges_| in @@ -103,6 +142,7 @@ static void DumpHistogramsToVlog(void* instance); static HistogramMap* histograms_; + static CallbackMap* callbacks_; static RangesMap* ranges_; // Lock protects access to above maps.
diff --git a/metrics/statistics_recorder_unittest.cc b/metrics/statistics_recorder_unittest.cc index d26df69..b18c580 100644 --- a/metrics/statistics_recorder_unittest.cc +++ b/metrics/statistics_recorder_unittest.cc
@@ -4,9 +4,11 @@ #include <vector> +#include "base/bind.h" #include "base/json/json_reader.h" #include "base/memory/scoped_ptr.h" -#include "base/metrics/histogram.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/sparse_histogram.h" #include "base/metrics/statistics_recorder.h" #include "base/values.h" #include "testing/gtest/include/gtest/gtest.h" @@ -312,4 +314,178 @@ EXPECT_TRUE(json.empty()); } +namespace { + +// CallbackCheckWrapper is simply a convenient way to check and store that +// a callback was actually run. +struct CallbackCheckWrapper { + CallbackCheckWrapper() : called(false), last_histogram_value(0) {} + + void OnHistogramChanged(base::HistogramBase::Sample histogram_value) { + called = true; + last_histogram_value = histogram_value; + } + + bool called; + base::HistogramBase::Sample last_histogram_value; +}; + +} // namespace + +// Check that you can't overwrite the callback with another. +TEST_F(StatisticsRecorderTest, SetCallbackFailsWithoutHistogramTest) { + CallbackCheckWrapper callback_wrapper; + + bool result = base::StatisticsRecorder::SetCallback( + "TestHistogram", base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + EXPECT_TRUE(result); + + result = base::StatisticsRecorder::SetCallback( + "TestHistogram", base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + EXPECT_FALSE(result); +} + +// Check that you can't overwrite the callback with another. +TEST_F(StatisticsRecorderTest, SetCallbackFailsWithHistogramTest) { + HistogramBase* histogram = Histogram::FactoryGet("TestHistogram", 1, 1000, 10, + HistogramBase::kNoFlags); + EXPECT_TRUE(histogram); + + CallbackCheckWrapper callback_wrapper; + + bool result = base::StatisticsRecorder::SetCallback( + "TestHistogram", base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + EXPECT_TRUE(result); + EXPECT_EQ(histogram->flags() & base::HistogramBase::kCallbackExists, + base::HistogramBase::kCallbackExists); + + result = base::StatisticsRecorder::SetCallback( + "TestHistogram", base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + EXPECT_FALSE(result); + EXPECT_EQ(histogram->flags() & base::HistogramBase::kCallbackExists, + base::HistogramBase::kCallbackExists); + + histogram->Add(1); + + EXPECT_TRUE(callback_wrapper.called); +} + +// Check that you can't overwrite the callback with another. +TEST_F(StatisticsRecorderTest, ClearCallbackSuceedsWithHistogramTest) { + HistogramBase* histogram = Histogram::FactoryGet("TestHistogram", 1, 1000, 10, + HistogramBase::kNoFlags); + EXPECT_TRUE(histogram); + + CallbackCheckWrapper callback_wrapper; + + bool result = base::StatisticsRecorder::SetCallback( + "TestHistogram", base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + EXPECT_TRUE(result); + EXPECT_EQ(histogram->flags() & base::HistogramBase::kCallbackExists, + base::HistogramBase::kCallbackExists); + + base::StatisticsRecorder::ClearCallback("TestHistogram"); + EXPECT_EQ(histogram->flags() & base::HistogramBase::kCallbackExists, 0); + + histogram->Add(1); + + EXPECT_FALSE(callback_wrapper.called); +} + +// Check that callback is used. +TEST_F(StatisticsRecorderTest, CallbackUsedTest) { + { + HistogramBase* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags); + EXPECT_TRUE(histogram); + + CallbackCheckWrapper callback_wrapper; + + base::StatisticsRecorder::SetCallback( + "TestHistogram", base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + + histogram->Add(1); + + EXPECT_TRUE(callback_wrapper.called); + EXPECT_EQ(callback_wrapper.last_histogram_value, 1); + } + + { + HistogramBase* linear_histogram = LinearHistogram::FactoryGet( + "TestLinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags); + + CallbackCheckWrapper callback_wrapper; + + base::StatisticsRecorder::SetCallback( + "TestLinearHistogram", + base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + + linear_histogram->Add(1); + + EXPECT_TRUE(callback_wrapper.called); + EXPECT_EQ(callback_wrapper.last_histogram_value, 1); + } + + { + std::vector<int> custom_ranges; + custom_ranges.push_back(1); + custom_ranges.push_back(5); + HistogramBase* custom_histogram = CustomHistogram::FactoryGet( + "TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags); + + CallbackCheckWrapper callback_wrapper; + + base::StatisticsRecorder::SetCallback( + "TestCustomHistogram", + base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + + custom_histogram->Add(1); + + EXPECT_TRUE(callback_wrapper.called); + EXPECT_EQ(callback_wrapper.last_histogram_value, 1); + } + + { + HistogramBase* custom_histogram = SparseHistogram::FactoryGet( + "TestSparseHistogram", HistogramBase::kNoFlags); + + CallbackCheckWrapper callback_wrapper; + + base::StatisticsRecorder::SetCallback( + "TestSparseHistogram", + base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + + custom_histogram->Add(1); + + EXPECT_TRUE(callback_wrapper.called); + EXPECT_EQ(callback_wrapper.last_histogram_value, 1); + } +} + +// Check that setting a callback before the histogram exists works. +TEST_F(StatisticsRecorderTest, CallbackUsedBeforeHistogramCreatedTest) { + CallbackCheckWrapper callback_wrapper; + + base::StatisticsRecorder::SetCallback( + "TestHistogram", base::Bind(&CallbackCheckWrapper::OnHistogramChanged, + base::Unretained(&callback_wrapper))); + + HistogramBase* histogram = Histogram::FactoryGet("TestHistogram", 1, 1000, 10, + HistogramBase::kNoFlags); + EXPECT_TRUE(histogram); + histogram->Add(1); + + EXPECT_TRUE(callback_wrapper.called); + EXPECT_EQ(callback_wrapper.last_histogram_value, 1); +} + } // namespace base
diff --git a/pickle.cc b/pickle.cc index 09d42c9..cf4a865 100644 --- a/pickle.cc +++ b/pickle.cc
@@ -317,13 +317,17 @@ } void Pickle::Resize(size_t new_capacity) { - new_capacity = AlignInt(new_capacity, kPayloadUnit); - CHECK_NE(capacity_after_header_, kCapacityReadOnly); - void* p = realloc(header_, header_size_ + new_capacity); + capacity_after_header_ = AlignInt(new_capacity, kPayloadUnit); + void* p = realloc(header_, GetTotalAllocatedSize()); CHECK(p); header_ = reinterpret_cast<Header*>(p); - capacity_after_header_ = new_capacity; +} + +size_t Pickle::GetTotalAllocatedSize() const { + if (capacity_after_header_ == kCapacityReadOnly) + return 0; + return header_size_ + capacity_after_header_; } // static
diff --git a/pickle.h b/pickle.h index 7a6b0c8..c9fef71 100644 --- a/pickle.h +++ b/pickle.h
@@ -153,12 +153,18 @@ // Performs a deep copy. Pickle& operator=(const Pickle& other); - // Returns the size of the Pickle's data. + // Returns the number of bytes written in the Pickle, including the header. size_t size() const { return header_size_ + header_->payload_size; } // Returns the data for this Pickle. const void* data() const { return header_; } + // Returns the effective memory capacity of this Pickle, that is, the total + // number of bytes currently dynamically allocated or 0 in the case of a + // read-only Pickle. This should be used only for diagnostic / profiling + // purposes. + size_t GetTotalAllocatedSize() 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
diff --git a/port.h b/port.h deleted file mode 100644 index 56c4d4e..0000000 --- a/port.h +++ /dev/null
@@ -1,36 +0,0 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef BASE_PORT_H_ -#define BASE_PORT_H_ - -#include <stdarg.h> -#include "build/build_config.h" - -// DEPRECATED: Use ...LL and ...ULL suffixes. -// TODO(viettrungluu): Delete these. These are only here until |GG_(U)INT64_C| -// are deleted (some other header files (re)define |GG_(U)INT64_C|, so our -// definitions of them must exactly match theirs). -#ifdef COMPILER_MSVC -#define GG_LONGLONG(x) x##I64 -#define GG_ULONGLONG(x) x##UI64 -#else -#define GG_LONGLONG(x) x##LL -#define GG_ULONGLONG(x) x##ULL -#endif - -// DEPRECATED: In Chromium, we force-define __STDC_CONSTANT_MACROS, so you can -// just use the regular (U)INTn_C macros from <stdint.h>. -// TODO(viettrungluu): Remove the remaining GG_(U)INTn_C macros. -#define GG_INT64_C(x) GG_LONGLONG(x) -#define GG_UINT64_C(x) GG_ULONGLONG(x) - -// Define an OS-neutral wrapper for shared library entry points -#if defined(OS_WIN) -#define API_CALL __stdcall -#else -#define API_CALL -#endif - -#endif // BASE_PORT_H_
diff --git a/prefs/default_pref_store.cc b/prefs/default_pref_store.cc index 92abba1..efb4a75 100644 --- a/prefs/default_pref_store.cc +++ b/prefs/default_pref_store.cc
@@ -29,7 +29,7 @@ void DefaultPrefStore::SetDefaultValue(const std::string& key, scoped_ptr<Value> value) { DCHECK(!GetValue(key, NULL)); - prefs_.SetValue(key, value.release()); + prefs_.SetValue(key, value.Pass()); } void DefaultPrefStore::ReplaceDefaultValue(const std::string& key, @@ -37,7 +37,7 @@ const Value* old_value = NULL; GetValue(key, &old_value); bool notify = !old_value->Equals(value.get()); - prefs_.SetValue(key, value.release()); + prefs_.SetValue(key, value.Pass()); if (notify) FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); }
diff --git a/prefs/json_pref_store.cc b/prefs/json_pref_store.cc index c2ff425..354fd94 100644 --- a/prefs/json_pref_store.cc +++ b/prefs/json_pref_store.cc
@@ -221,31 +221,29 @@ } void JsonPrefStore::SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { DCHECK(CalledOnValidThread()); DCHECK(value); - scoped_ptr<base::Value> new_value(value); base::Value* old_value = NULL; prefs_->Get(key, &old_value); if (!old_value || !value->Equals(old_value)) { - prefs_->Set(key, new_value.Pass()); + prefs_->Set(key, value.Pass()); ReportValueChanged(key, flags); } } void JsonPrefStore::SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { DCHECK(CalledOnValidThread()); DCHECK(value); - scoped_ptr<base::Value> new_value(value); base::Value* old_value = NULL; prefs_->Get(key, &old_value); if (!old_value || !value->Equals(old_value)) { - prefs_->Set(key, new_value.Pass()); + prefs_->Set(key, value.Pass()); ScheduleWrite(flags); } }
diff --git a/prefs/json_pref_store.h b/prefs/json_pref_store.h index d6943e0..0be7702 100644 --- a/prefs/json_pref_store.h +++ b/prefs/json_pref_store.h
@@ -84,10 +84,10 @@ // PersistentPrefStore overrides: bool GetMutableValue(const std::string& key, base::Value** result) override; void SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; void SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; void RemoveValue(const std::string& key, uint32 flags) override; bool ReadOnly() const override;
diff --git a/prefs/json_pref_store_unittest.cc b/prefs/json_pref_store_unittest.cc index 4c7bc1f..5195a18 100644 --- a/prefs/json_pref_store_unittest.cc +++ b/prefs/json_pref_store_unittest.cc
@@ -192,7 +192,8 @@ EXPECT_EQ(base::FilePath::StringType(FILE_PATH_LITERAL("/usr/local/")), path); base::FilePath some_path(FILE_PATH_LITERAL("/usr/sbin/")); - pref_store->SetValue(kSomeDirectory, new StringValue(some_path.value()), + pref_store->SetValue(kSomeDirectory, + make_scoped_ptr(new StringValue(some_path.value())), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual)); EXPECT_TRUE(actual->GetAsString(&path)); @@ -204,7 +205,8 @@ EXPECT_TRUE(actual->GetAsBoolean(&boolean)); EXPECT_TRUE(boolean); - pref_store->SetValue(kNewWindowsInTabs, new FundamentalValue(false), + pref_store->SetValue(kNewWindowsInTabs, + make_scoped_ptr(new FundamentalValue(false)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual)); EXPECT_TRUE(actual->GetAsBoolean(&boolean)); @@ -214,15 +216,16 @@ int integer = 0; EXPECT_TRUE(actual->GetAsInteger(&integer)); EXPECT_EQ(20, integer); - pref_store->SetValue(kMaxTabs, new FundamentalValue(10), + pref_store->SetValue(kMaxTabs, make_scoped_ptr(new FundamentalValue(10)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual)); EXPECT_TRUE(actual->GetAsInteger(&integer)); EXPECT_EQ(10, integer); - pref_store->SetValue(kLongIntPref, - new StringValue(base::Int64ToString(214748364842LL)), - WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + pref_store->SetValue( + kLongIntPref, + make_scoped_ptr(new StringValue(base::Int64ToString(214748364842LL))), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(pref_store->GetValue(kLongIntPref, &actual)); EXPECT_TRUE(actual->GetAsString(&string_value)); int64 value; @@ -312,9 +315,9 @@ pref_file, message_loop_.task_runner(), scoped_ptr<PrefFilter>()); // Set some keys with empty values. - pref_store->SetValue("list", new base::ListValue, + pref_store->SetValue("list", make_scoped_ptr(new base::ListValue), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); - pref_store->SetValue("dict", new base::DictionaryValue, + pref_store->SetValue("dict", make_scoped_ptr(new base::DictionaryValue), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); // Write to file. @@ -343,9 +346,9 @@ scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore( pref_file, message_loop_.task_runner(), scoped_ptr<PrefFilter>()); - base::DictionaryValue* dict = new base::DictionaryValue; + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue); dict->SetString("key", "value"); - pref_store->SetValue("dict", dict, + pref_store->SetValue("dict", dict.Pass(), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); pref_store->RemoveValue("dict.key", @@ -845,7 +848,8 @@ // Set a normal pref and check that it gets scheduled to be written. ASSERT_FALSE(file_writer->HasPendingWrite()); - pref_store->SetValue("normal", new base::StringValue("normal"), + pref_store->SetValue("normal", + make_scoped_ptr(new base::StringValue("normal")), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); ASSERT_TRUE(file_writer->HasPendingWrite()); file_writer->DoScheduledWrite(); @@ -854,14 +858,15 @@ // Set a lossy pref and check that it is not scheduled to be written. // SetValue/RemoveValue. - pref_store->SetValue("lossy", new base::StringValue("lossy"), + pref_store->SetValue("lossy", make_scoped_ptr(new base::StringValue("lossy")), WriteablePrefStore::LOSSY_PREF_WRITE_FLAG); ASSERT_FALSE(file_writer->HasPendingWrite()); pref_store->RemoveValue("lossy", WriteablePrefStore::LOSSY_PREF_WRITE_FLAG); ASSERT_FALSE(file_writer->HasPendingWrite()); // SetValueSilently/RemoveValueSilently. - pref_store->SetValueSilently("lossy", new base::StringValue("lossy"), + pref_store->SetValueSilently("lossy", + make_scoped_ptr(new base::StringValue("lossy")), WriteablePrefStore::LOSSY_PREF_WRITE_FLAG); ASSERT_FALSE(file_writer->HasPendingWrite()); pref_store->RemoveValueSilently("lossy", @@ -869,7 +874,7 @@ ASSERT_FALSE(file_writer->HasPendingWrite()); // ReportValueChanged. - pref_store->SetValue("lossy", new base::StringValue("lossy"), + pref_store->SetValue("lossy", make_scoped_ptr(new base::StringValue("lossy")), WriteablePrefStore::LOSSY_PREF_WRITE_FLAG); ASSERT_FALSE(file_writer->HasPendingWrite()); pref_store->ReportValueChanged("lossy", @@ -890,12 +895,13 @@ // Set a lossy pref and check that it is not scheduled to be written. ASSERT_FALSE(file_writer->HasPendingWrite()); - pref_store->SetValue("lossy", new base::StringValue("lossy"), + pref_store->SetValue("lossy", make_scoped_ptr(new base::StringValue("lossy")), WriteablePrefStore::LOSSY_PREF_WRITE_FLAG); ASSERT_FALSE(file_writer->HasPendingWrite()); // Set a normal pref and check that it is scheduled to be written. - pref_store->SetValue("normal", new base::StringValue("normal"), + pref_store->SetValue("normal", + make_scoped_ptr(new base::StringValue("normal")), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); ASSERT_TRUE(file_writer->HasPendingWrite()); @@ -912,12 +918,13 @@ // Set a normal pref and check that it is scheduled to be written. ASSERT_FALSE(file_writer->HasPendingWrite()); - pref_store->SetValue("normal", new base::StringValue("normal"), + pref_store->SetValue("normal", + make_scoped_ptr(new base::StringValue("normal")), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); ASSERT_TRUE(file_writer->HasPendingWrite()); // Set a lossy pref and check that the write is still scheduled. - pref_store->SetValue("lossy", new base::StringValue("lossy"), + pref_store->SetValue("lossy", make_scoped_ptr(new base::StringValue("lossy")), WriteablePrefStore::LOSSY_PREF_WRITE_FLAG); ASSERT_TRUE(file_writer->HasPendingWrite()); @@ -933,7 +940,7 @@ ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store); // Set a lossy pref and check that it is not scheduled to be written. - pref_store->SetValue("lossy", new base::StringValue("lossy"), + pref_store->SetValue("lossy", make_scoped_ptr(new base::StringValue("lossy")), WriteablePrefStore::LOSSY_PREF_WRITE_FLAG); ASSERT_FALSE(file_writer->HasPendingWrite());
diff --git a/prefs/overlay_user_pref_store.cc b/prefs/overlay_user_pref_store.cc index 4c236f1..d76b537 100644 --- a/prefs/overlay_user_pref_store.cc +++ b/prefs/overlay_user_pref_store.cc
@@ -58,31 +58,31 @@ return false; *result = underlay_value->DeepCopy(); - overlay_.SetValue(key, *result); + overlay_.SetValue(key, make_scoped_ptr(*result)); return true; } void OverlayUserPrefStore::SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { if (!ShallBeStoredInOverlay(key)) { - underlay_->SetValue(GetUnderlayKey(key), value, flags); + underlay_->SetValue(GetUnderlayKey(key), value.Pass(), flags); return; } - if (overlay_.SetValue(key, value)) + if (overlay_.SetValue(key, value.Pass())) ReportValueChanged(key, flags); } void OverlayUserPrefStore::SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { if (!ShallBeStoredInOverlay(key)) { - underlay_->SetValueSilently(GetUnderlayKey(key), value, flags); + underlay_->SetValueSilently(GetUnderlayKey(key), value.Pass(), flags); return; } - overlay_.SetValue(key, value); + overlay_.SetValue(key, value.Pass()); } void OverlayUserPrefStore::RemoveValue(const std::string& key, uint32 flags) {
diff --git a/prefs/overlay_user_pref_store.h b/prefs/overlay_user_pref_store.h index 885da08..737b426 100644 --- a/prefs/overlay_user_pref_store.h +++ b/prefs/overlay_user_pref_store.h
@@ -40,10 +40,10 @@ // Methods of PersistentPrefStore. bool GetMutableValue(const std::string& key, base::Value** result) override; void SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; void SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; void RemoveValue(const std::string& key, uint32 flags) override; bool ReadOnly() const override;
diff --git a/prefs/overlay_user_pref_store_unittest.cc b/prefs/overlay_user_pref_store_unittest.cc index 06b4ec9..bf5e6a5 100644 --- a/prefs/overlay_user_pref_store_unittest.cc +++ b/prefs/overlay_user_pref_store_unittest.cc
@@ -48,22 +48,22 @@ overlay_->AddObserver(&obs); // Check that underlay first value is reported. - underlay_->SetValue(overlay_key, new FundamentalValue(42), + underlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(42)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(overlay_key); // Check that underlay overwriting is reported. - underlay_->SetValue(overlay_key, new FundamentalValue(43), + underlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(43)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(overlay_key); // Check that overwriting change in overlay is reported. - overlay_->SetValue(overlay_key, new FundamentalValue(44), + overlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(44)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(overlay_key); // Check that hidden underlay change is not reported. - underlay_->SetValue(overlay_key, new FundamentalValue(45), + underlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(45)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); @@ -78,16 +78,17 @@ obs.VerifyAndResetChangedKey(overlay_key); // Check respecting of silence. - overlay_->SetValueSilently(overlay_key, new FundamentalValue(46), + overlay_->SetValueSilently(overlay_key, + make_scoped_ptr(new FundamentalValue(46)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); overlay_->RemoveObserver(&obs); // Check successful unsubscription. - underlay_->SetValue(overlay_key, new FundamentalValue(47), + underlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(47)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); - overlay_->SetValue(overlay_key, new FundamentalValue(48), + overlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(48)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); } @@ -97,7 +98,7 @@ EXPECT_FALSE(overlay_->GetValue(overlay_key, &value)); EXPECT_FALSE(underlay_->GetValue(overlay_key, &value)); - underlay_->SetValue(overlay_key, new FundamentalValue(42), + underlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(42)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); // Value shines through: @@ -107,7 +108,7 @@ EXPECT_TRUE(underlay_->GetValue(overlay_key, &value)); EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); - overlay_->SetValue(overlay_key, new FundamentalValue(43), + overlay_->SetValue(overlay_key, make_scoped_ptr(new FundamentalValue(43)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(overlay_->GetValue(overlay_key, &value)); @@ -129,7 +130,7 @@ // Check that GetMutableValue does not return the dictionary of the underlay. TEST_F(OverlayUserPrefStoreTest, ModifyDictionaries) { - underlay_->SetValue(overlay_key, new DictionaryValue, + underlay_->SetValue(overlay_key, make_scoped_ptr(new DictionaryValue), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); Value* modify = NULL; @@ -159,12 +160,12 @@ const Value* value = NULL; // Check that underlay first value is reported. - underlay_->SetValue(regular_key, new FundamentalValue(42), + underlay_->SetValue(regular_key, make_scoped_ptr(new FundamentalValue(42)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(regular_key); // Check that underlay overwriting is reported. - underlay_->SetValue(regular_key, new FundamentalValue(43), + underlay_->SetValue(regular_key, make_scoped_ptr(new FundamentalValue(43)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(regular_key); @@ -173,7 +174,7 @@ EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); // Check that overwriting change in overlay is reported. - overlay_->SetValue(regular_key, new FundamentalValue(44), + overlay_->SetValue(regular_key, make_scoped_ptr(new FundamentalValue(44)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(regular_key); @@ -193,16 +194,17 @@ EXPECT_FALSE(underlay_->GetValue(regular_key, &value)); // Check respecting of silence. - overlay_->SetValueSilently(regular_key, new FundamentalValue(46), + overlay_->SetValueSilently(regular_key, + make_scoped_ptr(new FundamentalValue(46)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); overlay_->RemoveObserver(&obs); // Check successful unsubscription. - underlay_->SetValue(regular_key, new FundamentalValue(47), + underlay_->SetValue(regular_key, make_scoped_ptr(new FundamentalValue(47)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); - overlay_->SetValue(regular_key, new FundamentalValue(48), + overlay_->SetValue(regular_key, make_scoped_ptr(new FundamentalValue(48)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); } @@ -216,12 +218,14 @@ // Check that if there is no override in the overlay, changing underlay value // is reported as changing an overlay value. - underlay_->SetValue(mapped_underlay_key, new FundamentalValue(42), + underlay_->SetValue(mapped_underlay_key, + make_scoped_ptr(new FundamentalValue(42)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(mapped_overlay_key); // Check that underlay overwriting is reported. - underlay_->SetValue(mapped_underlay_key, new FundamentalValue(43), + underlay_->SetValue(mapped_underlay_key, + make_scoped_ptr(new FundamentalValue(43)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(mapped_overlay_key); @@ -233,7 +237,8 @@ EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); // Check that overwriting change in overlay is reported. - overlay_->SetValue(mapped_overlay_key, new FundamentalValue(44), + overlay_->SetValue(mapped_overlay_key, + make_scoped_ptr(new FundamentalValue(44)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); obs.VerifyAndResetChangedKey(mapped_overlay_key); @@ -247,7 +252,8 @@ EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); // Check that hidden underlay change is not reported. - underlay_->SetValue(mapped_underlay_key, new FundamentalValue(45), + underlay_->SetValue(mapped_underlay_key, + make_scoped_ptr(new FundamentalValue(45)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); @@ -266,16 +272,19 @@ EXPECT_FALSE(overlay_->GetValue(mapped_underlay_key, &value)); // Check respecting of silence. - overlay_->SetValueSilently(mapped_overlay_key, new FundamentalValue(46), + overlay_->SetValueSilently(mapped_overlay_key, + make_scoped_ptr(new FundamentalValue(46)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); overlay_->RemoveObserver(&obs); // Check successful unsubscription. - underlay_->SetValue(mapped_underlay_key, new FundamentalValue(47), + underlay_->SetValue(mapped_underlay_key, + make_scoped_ptr(new FundamentalValue(47)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); - overlay_->SetValue(mapped_overlay_key, new FundamentalValue(48), + overlay_->SetValue(mapped_overlay_key, + make_scoped_ptr(new FundamentalValue(48)), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); EXPECT_TRUE(obs.changed_keys.empty()); }
diff --git a/prefs/pref_service.cc b/prefs/pref_service.cc index a9749b2..c53b896 100644 --- a/prefs/pref_service.cc +++ b/prefs/pref_service.cc
@@ -471,7 +471,8 @@ } else { NOTREACHED(); } - user_pref_store_->SetValueSilently(path, value, GetWriteFlags(pref)); + user_pref_store_->SetValueSilently(path, make_scoped_ptr(value), + GetWriteFlags(pref)); } return value; } @@ -498,7 +499,7 @@ return; } - user_pref_store_->SetValue(path, owned_value.release(), GetWriteFlags(pref)); + user_pref_store_->SetValue(path, owned_value.Pass(), GetWriteFlags(pref)); } void PrefService::UpdateCommandLinePrefStore(PrefStore* command_line_store) {
diff --git a/prefs/pref_service_unittest.cc b/prefs/pref_service_unittest.cc index 262d7e9..2506b1d 100644 --- a/prefs/pref_service_unittest.cc +++ b/prefs/pref_service_unittest.cc
@@ -239,17 +239,15 @@ } void SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override { SetLastWriteFlags(flags); - delete value; } void SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override { SetLastWriteFlags(flags); - delete value; } void RemoveValue(const std::string& key, uint32 flags) override {
diff --git a/prefs/pref_value_map.cc b/prefs/pref_value_map.cc index 5f2dc50..93eadb7 100644 --- a/prefs/pref_value_map.cc +++ b/prefs/pref_value_map.cc
@@ -13,60 +13,43 @@ PrefValueMap::PrefValueMap() {} -PrefValueMap::~PrefValueMap() { - Clear(); -} +PrefValueMap::~PrefValueMap() {} bool PrefValueMap::GetValue(const std::string& key, const base::Value** value) const { - const Map::const_iterator entry = prefs_.find(key); - if (entry == prefs_.end()) - return false; + const base::Value* got_value = prefs_.get(key); + if (value && got_value) + *value = got_value; - if (value) - *value = entry->second; - return true; + return !!got_value; } bool PrefValueMap::GetValue(const std::string& key, base::Value** value) { - const Map::const_iterator entry = prefs_.find(key); - if (entry == prefs_.end()) - return false; + base::Value* got_value = prefs_.get(key); + if (value && got_value) + *value = got_value; - if (value) - *value = entry->second; - return true; + return !!got_value; } -bool PrefValueMap::SetValue(const std::string& key, base::Value* value) { +bool PrefValueMap::SetValue(const std::string& key, + scoped_ptr<base::Value> value) { DCHECK(value); - auto result = prefs_.insert(std::make_pair(key, value)); - if (result.second) - return true; - scoped_ptr<base::Value> value_ptr(value); - const Map::iterator& entry = result.first; - if (base::Value::Equals(entry->second, value)) + base::Value* old_value = prefs_.get(key); + if (old_value && value->Equals(old_value)) return false; - delete entry->second; - entry->second = value_ptr.release(); - + prefs_.set(key, value.Pass()); return true; } bool PrefValueMap::RemoveValue(const std::string& key) { - const Map::iterator entry = prefs_.find(key); - if (entry == prefs_.end()) - return false; - - delete entry->second; - prefs_.erase(entry); - return true; + return prefs_.erase(key) != 0; } void PrefValueMap::Clear() { - STLDeleteValues(&prefs_); + prefs_.clear(); } void PrefValueMap::Swap(PrefValueMap* other) { @@ -96,7 +79,7 @@ } void PrefValueMap::SetBoolean(const std::string& key, bool value) { - SetValue(key, new base::FundamentalValue(value)); + SetValue(key, make_scoped_ptr(new base::FundamentalValue(value))); } bool PrefValueMap::GetString(const std::string& key, @@ -107,7 +90,7 @@ void PrefValueMap::SetString(const std::string& key, const std::string& value) { - SetValue(key, new base::StringValue(value)); + SetValue(key, make_scoped_ptr(new base::StringValue(value))); } bool PrefValueMap::GetInteger(const std::string& key, int* value) const { @@ -116,11 +99,11 @@ } void PrefValueMap::SetInteger(const std::string& key, const int value) { - SetValue(key, new base::FundamentalValue(value)); + SetValue(key, make_scoped_ptr(new base::FundamentalValue(value))); } void PrefValueMap::SetDouble(const std::string& key, const double value) { - SetValue(key, new base::FundamentalValue(value)); + SetValue(key, make_scoped_ptr(new base::FundamentalValue(value))); } void PrefValueMap::GetDifferingKeys(
diff --git a/prefs/pref_value_map.h b/prefs/pref_value_map.h index 12b30c6..7d43f2b 100644 --- a/prefs/pref_value_map.h +++ b/prefs/pref_value_map.h
@@ -9,7 +9,8 @@ #include <vector> #include "base/basictypes.h" -#include "base/containers/hash_tables.h" +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/memory/scoped_ptr.h" #include "base/prefs/base_prefs_export.h" namespace base { @@ -19,7 +20,7 @@ // A generic string to value map used by the PrefStore implementations. class BASE_PREFS_EXPORT PrefValueMap { public: - using Map = base::hash_map<std::string, base::Value*>; + using Map = base::ScopedPtrHashMap<std::string, scoped_ptr<base::Value>>; using iterator = Map::iterator; using const_iterator = Map::const_iterator; @@ -32,9 +33,9 @@ bool GetValue(const std::string& key, const base::Value** value) const; bool GetValue(const std::string& key, base::Value** value); - // Sets a new |value| for |key|. Takes ownership of |value|, which must be - // non-NULL. Returns true if the value changed. - bool SetValue(const std::string& key, base::Value* value); + // Sets a new |value| for |key|. |value| must be non-null. Returns true if the + // value changed. + bool SetValue(const std::string& key, scoped_ptr<base::Value> value); // Removes the value for |key| from the map. Returns true if a value was // removed.
diff --git a/prefs/pref_value_map_unittest.cc b/prefs/pref_value_map_unittest.cc index 82499da..f78c999 100644 --- a/prefs/pref_value_map_unittest.cc +++ b/prefs/pref_value_map_unittest.cc
@@ -16,9 +16,9 @@ EXPECT_FALSE(map.GetValue("key", &result)); EXPECT_FALSE(result); - EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); - EXPECT_FALSE(map.SetValue("key", new StringValue("test"))); - EXPECT_TRUE(map.SetValue("key", new StringValue("hi mom!"))); + EXPECT_TRUE(map.SetValue("key", make_scoped_ptr(new StringValue("test")))); + EXPECT_FALSE(map.SetValue("key", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE(map.SetValue("key", make_scoped_ptr(new StringValue("hi mom!")))); EXPECT_TRUE(map.GetValue("key", &result)); EXPECT_TRUE(StringValue("hi mom!").Equals(result)); @@ -26,7 +26,7 @@ TEST(PrefValueMapTest, GetAndSetIntegerValue) { PrefValueMap map; - ASSERT_TRUE(map.SetValue("key", new FundamentalValue(5))); + ASSERT_TRUE(map.SetValue("key", make_scoped_ptr(new FundamentalValue(5)))); int int_value = 0; EXPECT_TRUE(map.GetInteger("key", &int_value)); @@ -39,7 +39,7 @@ TEST(PrefValueMapTest, SetDoubleValue) { PrefValueMap map; - ASSERT_TRUE(map.SetValue("key", new FundamentalValue(5.5))); + ASSERT_TRUE(map.SetValue("key", make_scoped_ptr(new FundamentalValue(5.5)))); const Value* result = NULL; ASSERT_TRUE(map.GetValue("key", &result)); @@ -52,7 +52,7 @@ PrefValueMap map; EXPECT_FALSE(map.RemoveValue("key")); - EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.SetValue("key", make_scoped_ptr(new StringValue("test")))); EXPECT_TRUE(map.GetValue("key", NULL)); EXPECT_TRUE(map.RemoveValue("key")); @@ -63,7 +63,7 @@ TEST(PrefValueMapTest, Clear) { PrefValueMap map; - EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.SetValue("key", make_scoped_ptr(new StringValue("test")))); EXPECT_TRUE(map.GetValue("key", NULL)); map.Clear(); @@ -73,9 +73,12 @@ TEST(PrefValueMapTest, GetDifferingKeys) { PrefValueMap reference; - EXPECT_TRUE(reference.SetValue("b", new StringValue("test"))); - EXPECT_TRUE(reference.SetValue("c", new StringValue("test"))); - EXPECT_TRUE(reference.SetValue("e", new StringValue("test"))); + EXPECT_TRUE( + reference.SetValue("b", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE( + reference.SetValue("c", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE( + reference.SetValue("e", make_scoped_ptr(new StringValue("test")))); PrefValueMap check; std::vector<std::string> differing_paths; @@ -87,9 +90,9 @@ expected_differing_paths.push_back("e"); EXPECT_EQ(expected_differing_paths, differing_paths); - EXPECT_TRUE(check.SetValue("a", new StringValue("test"))); - EXPECT_TRUE(check.SetValue("c", new StringValue("test"))); - EXPECT_TRUE(check.SetValue("d", new StringValue("test"))); + EXPECT_TRUE(check.SetValue("a", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE(check.SetValue("c", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE(check.SetValue("d", make_scoped_ptr(new StringValue("test")))); reference.GetDifferingKeys(&check, &differing_paths); expected_differing_paths.clear(); @@ -102,14 +105,20 @@ TEST(PrefValueMapTest, SwapTwoMaps) { PrefValueMap first_map; - EXPECT_TRUE(first_map.SetValue("a", new StringValue("test"))); - EXPECT_TRUE(first_map.SetValue("b", new StringValue("test"))); - EXPECT_TRUE(first_map.SetValue("c", new StringValue("test"))); + EXPECT_TRUE( + first_map.SetValue("a", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE( + first_map.SetValue("b", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE( + first_map.SetValue("c", make_scoped_ptr(new StringValue("test")))); PrefValueMap second_map; - EXPECT_TRUE(second_map.SetValue("d", new StringValue("test"))); - EXPECT_TRUE(second_map.SetValue("e", new StringValue("test"))); - EXPECT_TRUE(second_map.SetValue("f", new StringValue("test"))); + EXPECT_TRUE( + second_map.SetValue("d", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE( + second_map.SetValue("e", make_scoped_ptr(new StringValue("test")))); + EXPECT_TRUE( + second_map.SetValue("f", make_scoped_ptr(new StringValue("test")))); first_map.Swap(&second_map);
diff --git a/prefs/testing_pref_service.h b/prefs/testing_pref_service.h index 7587383..cbc978d 100644 --- a/prefs/testing_pref_service.h +++ b/prefs/testing_pref_service.h
@@ -182,7 +182,7 @@ SetPref(TestingPrefStore* pref_store, const std::string& path, base::Value* value) { - pref_store->SetValue(path, value, + pref_store->SetValue(path, make_scoped_ptr(value), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); }
diff --git a/prefs/testing_pref_store.cc b/prefs/testing_pref_store.cc index 1a1e6bc..2322f4e 100644 --- a/prefs/testing_pref_store.cc +++ b/prefs/testing_pref_store.cc
@@ -43,18 +43,18 @@ } void TestingPrefStore::SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { - if (prefs_.SetValue(key, value)) { + if (prefs_.SetValue(key, value.Pass())) { committed_ = false; NotifyPrefValueChanged(key); } } void TestingPrefStore::SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { - if (prefs_.SetValue(key, value)) + if (prefs_.SetValue(key, value.Pass())) committed_ = false; } @@ -115,15 +115,18 @@ void TestingPrefStore::SetString(const std::string& key, const std::string& value) { - SetValue(key, new base::StringValue(value), DEFAULT_PREF_WRITE_FLAGS); + SetValue(key, make_scoped_ptr(new base::StringValue(value)), + DEFAULT_PREF_WRITE_FLAGS); } void TestingPrefStore::SetInteger(const std::string& key, int value) { - SetValue(key, new base::FundamentalValue(value), DEFAULT_PREF_WRITE_FLAGS); + SetValue(key, make_scoped_ptr(new base::FundamentalValue(value)), + DEFAULT_PREF_WRITE_FLAGS); } void TestingPrefStore::SetBoolean(const std::string& key, bool value) { - SetValue(key, new base::FundamentalValue(value), DEFAULT_PREF_WRITE_FLAGS); + SetValue(key, make_scoped_ptr(new base::FundamentalValue(value)), + DEFAULT_PREF_WRITE_FLAGS); } bool TestingPrefStore::GetString(const std::string& key,
diff --git a/prefs/testing_pref_store.h b/prefs/testing_pref_store.h index f43a030..72e61b3 100644 --- a/prefs/testing_pref_store.h +++ b/prefs/testing_pref_store.h
@@ -32,10 +32,10 @@ bool GetMutableValue(const std::string& key, base::Value** result) override; void ReportValueChanged(const std::string& key, uint32 flags) override; void SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; void SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; void RemoveValue(const std::string& key, uint32 flags) override; bool ReadOnly() const override;
diff --git a/prefs/value_map_pref_store.cc b/prefs/value_map_pref_store.cc index d850150..f22f93a 100644 --- a/prefs/value_map_pref_store.cc +++ b/prefs/value_map_pref_store.cc
@@ -29,9 +29,9 @@ } void ValueMapPrefStore::SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { - if (prefs_.SetValue(key, value)) + if (prefs_.SetValue(key, value.Pass())) FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); } @@ -51,9 +51,9 @@ } void ValueMapPrefStore::SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) { - prefs_.SetValue(key, value); + prefs_.SetValue(key, value.Pass()); } ValueMapPrefStore::~ValueMapPrefStore() {}
diff --git a/prefs/value_map_pref_store.h b/prefs/value_map_pref_store.h index 9d8fa3e..badfef7 100644 --- a/prefs/value_map_pref_store.h +++ b/prefs/value_map_pref_store.h
@@ -29,13 +29,13 @@ // WriteablePrefStore overrides: void SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; void RemoveValue(const std::string& key, uint32 flags) override; bool GetMutableValue(const std::string& key, base::Value** value) override; void ReportValueChanged(const std::string& key, uint32 flags) override; void SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) override; protected:
diff --git a/prefs/writeable_pref_store.h b/prefs/writeable_pref_store.h index d85b4c8..cde3c84 100644 --- a/prefs/writeable_pref_store.h +++ b/prefs/writeable_pref_store.h
@@ -8,6 +8,7 @@ #include <string> #include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" #include "base/prefs/pref_store.h" namespace base { @@ -30,10 +31,10 @@ WriteablePrefStore() {} - // Sets a |value| for |key| in the store. Assumes ownership of |value|, which - // must be non-NULL. |flags| is a bitmask of PrefWriteFlags. + // Sets a |value| for |key| in the store. |value| must be non-NULL. |flags| is + // a bitmask of PrefWriteFlags. virtual void SetValue(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) = 0; // Removes the value for |key|. @@ -56,7 +57,7 @@ // tests rely on the number of notifications generated. |flags| is a bitmask // of PrefWriteFlags. virtual void SetValueSilently(const std::string& key, - base::Value* value, + scoped_ptr<base::Value> value, uint32 flags) = 0; protected:
diff --git a/process/BUILD.gn b/process/BUILD.gn index a362cbb..91f2725 100644 --- a/process/BUILD.gn +++ b/process/BUILD.gn
@@ -77,7 +77,7 @@ set_sources_assignment_filter(sources_assignment_filter) } - if (is_nacl) { + if (is_nacl || is_ios) { sources -= [ "kill.cc", "kill.h", @@ -93,6 +93,10 @@ ] } + if (is_ios) { + sources += [ "process_metrics.cc" ] + } + configs += [ "//base:base_implementation" ] deps = [
diff --git a/process/internal_linux.cc b/process/internal_linux.cc index d2e9ec5..4f3fcac 100644 --- a/process/internal_linux.cc +++ b/process/internal_linux.cc
@@ -25,8 +25,8 @@ const char kStatFile[] = "stat"; -base::FilePath GetProcPidDir(pid_t pid) { - return base::FilePath(kProcDir).Append(IntToString(pid)); +FilePath GetProcPidDir(pid_t pid) { + return FilePath(kProcDir).Append(IntToString(pid)); } pid_t ProcDirSlotToPid(const char* d_name) { @@ -106,7 +106,7 @@ typedef std::map<std::string, std::string> ProcStatMap; void ParseProcStat(const std::string& contents, ProcStatMap* output) { - base::StringPairs key_value_pairs; + StringPairs key_value_pairs; SplitStringIntoKeyValuePairs(contents, ' ', '\n', &key_value_pairs); for (size_t i = 0; i < key_value_pairs.size(); ++i) { output->insert(key_value_pairs[i]);
diff --git a/process/launch.h b/process/launch.h index 56f27a8..0e42cd0 100644 --- a/process/launch.h +++ b/process/launch.h
@@ -297,7 +297,7 @@ // binary. This should not be called in production/released code. BASE_EXPORT LaunchOptions LaunchOptionsForTest(); -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_NACL_NONSFI) // A wrapper for clone with fork-like behavior, meaning that it returns the // child's pid in the parent and 0 in the child. |flags|, |ptid|, and |ctid| are // as in the clone system call (the CLONE_VM flag is not supported).
diff --git a/process/launch_posix.cc b/process/launch_posix.cc index 77edc12..ed9faeb 100644 --- a/process/launch_posix.cc +++ b/process/launch_posix.cc
@@ -65,6 +65,8 @@ namespace base { +#if !defined(OS_NACL_NONSFI) + namespace { // Get the process's "environment" (i.e. the thing that setenv/getenv @@ -188,55 +190,6 @@ } #endif // !defined(OS_LINUX) || // (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) - -#if defined(OS_LINUX) -bool IsRunningOnValgrind() { - return RUNNING_ON_VALGRIND; -} - -// This function runs on the stack specified on the clone call. It uses longjmp -// to switch back to the original stack so the child can return from sys_clone. -int CloneHelper(void* arg) { - jmp_buf* env_ptr = reinterpret_cast<jmp_buf*>(arg); - longjmp(*env_ptr, 1); - - // Should not be reached. - RAW_CHECK(false); - return 1; -} - -// This function is noinline to ensure that stack_buf is below the stack pointer -// that is saved when setjmp is called below. This is needed because when -// compiled with FORTIFY_SOURCE, glibc's longjmp checks that the stack is moved -// upwards. See crbug.com/442912 for more details. -#if defined(ADDRESS_SANITIZER) -// Disable AddressSanitizer instrumentation for this function to make sure -// |stack_buf| is allocated on thread stack instead of ASan's fake stack. -// Under ASan longjmp() will attempt to clean up the area between the old and -// new stack pointers and print a warning that may confuse the user. -__attribute__((no_sanitize_address)) -#endif -NOINLINE pid_t CloneAndLongjmpInChild(unsigned long flags, - pid_t* ptid, - pid_t* ctid, - jmp_buf* env) { - // We use the libc clone wrapper instead of making the syscall - // directly because making the syscall may fail to update the libc's - // internal pid cache. The libc interface unfortunately requires - // specifying a new stack, so we use setjmp/longjmp to emulate - // fork-like behavior. - char stack_buf[PTHREAD_STACK_MIN]; -#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ - defined(ARCH_CPU_MIPS64_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY) - // The stack grows downward. - void* stack = stack_buf + sizeof(stack_buf); -#else -#error "Unsupported architecture" -#endif - return clone(&CloneHelper, stack, flags, env, ptid, nullptr, ctid); -} -#endif // defined(OS_LINUX) - } // anonymous namespace // Functor for |ScopedDIR| (below). @@ -741,7 +694,60 @@ return result == EXECUTE_SUCCESS; } -#if defined(OS_LINUX) +#endif // !defined(OS_NACL_NONSFI) + +#if defined(OS_LINUX) || defined(OS_NACL_NONSFI) +namespace { + +bool IsRunningOnValgrind() { + return RUNNING_ON_VALGRIND; +} + +// This function runs on the stack specified on the clone call. It uses longjmp +// to switch back to the original stack so the child can return from sys_clone. +int CloneHelper(void* arg) { + jmp_buf* env_ptr = reinterpret_cast<jmp_buf*>(arg); + longjmp(*env_ptr, 1); + + // Should not be reached. + RAW_CHECK(false); + return 1; +} + +// This function is noinline to ensure that stack_buf is below the stack pointer +// that is saved when setjmp is called below. This is needed because when +// compiled with FORTIFY_SOURCE, glibc's longjmp checks that the stack is moved +// upwards. See crbug.com/442912 for more details. +#if defined(ADDRESS_SANITIZER) +// Disable AddressSanitizer instrumentation for this function to make sure +// |stack_buf| is allocated on thread stack instead of ASan's fake stack. +// Under ASan longjmp() will attempt to clean up the area between the old and +// new stack pointers and print a warning that may confuse the user. +__attribute__((no_sanitize_address)) +#endif +NOINLINE pid_t +CloneAndLongjmpInChild(unsigned long flags, + pid_t* ptid, + pid_t* ctid, + jmp_buf* env) { + // We use the libc clone wrapper instead of making the syscall + // directly because making the syscall may fail to update the libc's + // internal pid cache. The libc interface unfortunately requires + // specifying a new stack, so we use setjmp/longjmp to emulate + // fork-like behavior. + char stack_buf[PTHREAD_STACK_MIN]; +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS64_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY) + // The stack grows downward. + void* stack = stack_buf + sizeof(stack_buf); +#else +#error "Unsupported architecture" +#endif + return clone(&CloneHelper, stack, flags, env, ptid, nullptr, ctid); +} + +} // anonymous namespace + pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid) { const bool clone_tls_used = flags & CLONE_SETTLS; const bool invalid_ctid = @@ -780,6 +786,6 @@ return 0; } -#endif // defined(OS_LINUX) +#endif // defined(OS_LINUX) || defined(OS_NACL_NONSFI) } // namespace base
diff --git a/process/launch_win.cc b/process/launch_win.cc index ebc19b8..fa59f1a 100644 --- a/process/launch_win.cc +++ b/process/launch_win.cc
@@ -238,7 +238,7 @@ const string16 file = cmdline.GetProgram().value(); const string16 arguments = cmdline.GetArgumentsString(); - SHELLEXECUTEINFO shex_info = {0}; + SHELLEXECUTEINFO shex_info = {}; shex_info.cbSize = sizeof(shex_info); shex_info.fMask = SEE_MASK_NOCLOSEPROCESS; shex_info.hwnd = GetActiveWindow(); @@ -261,7 +261,7 @@ } bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags) { - JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {}; limit_info.BasicLimitInformation.LimitFlags = limit_flags; return 0 != SetInformationJobObject( job_object,
diff --git a/process/process_iterator_freebsd.cc b/process/process_iterator_freebsd.cc index 5b1e2ab..51427b3 100644 --- a/process/process_iterator_freebsd.cc +++ b/process/process_iterator_freebsd.cc
@@ -9,6 +9,7 @@ #include <unistd.h> #include "base/logging.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" namespace base { @@ -88,7 +89,8 @@ std::string delimiters; delimiters.push_back('\0'); - Tokenize(data, delimiters, &entry_.cmd_line_args_); + entry_.cmd_line_args_ = + SplitString(data, delimiters, KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); size_t exec_name_end = data.find('\0'); if (exec_name_end == std::string::npos) {
diff --git a/process/process_iterator_linux.cc b/process/process_iterator_linux.cc index 3319552..5c8ca08 100644 --- a/process/process_iterator_linux.cc +++ b/process/process_iterator_linux.cc
@@ -7,6 +7,7 @@ #include "base/files/file_util.h" #include "base/logging.h" #include "base/process/internal_linux.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/threading/thread_restrictions.h" @@ -48,7 +49,8 @@ return false; std::string delimiters; delimiters.push_back('\0'); - Tokenize(cmd_line, delimiters, proc_cmd_line_args); + *proc_cmd_line_args = + SplitString(cmd_line, delimiters, KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); return true; }
diff --git a/process/process_iterator_mac.cc b/process/process_iterator_mac.cc index e35c2ae..add62d5 100644 --- a/process/process_iterator_mac.cc +++ b/process/process_iterator_mac.cc
@@ -10,6 +10,7 @@ #include <unistd.h> #include "base/logging.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" namespace base { @@ -99,7 +100,8 @@ // |entry_.cmd_line_args_|. std::string delimiters; delimiters.push_back('\0'); - Tokenize(data, delimiters, &entry_.cmd_line_args_); + entry_.cmd_line_args_ = + SplitString(data, delimiters, KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); // |data| starts with the full executable path followed by a null character. // We search for the first instance of '\0' and extract everything before it
diff --git a/process/process_iterator_openbsd.cc b/process/process_iterator_openbsd.cc index 7c44eb1..b0d52b8 100644 --- a/process/process_iterator_openbsd.cc +++ b/process/process_iterator_openbsd.cc
@@ -8,6 +8,7 @@ #include <sys/sysctl.h> #include "base/logging.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" namespace base { @@ -92,7 +93,8 @@ // |entry_.cmd_line_args_|. std::string delimiters; delimiters.push_back('\0'); - Tokenize(data, delimiters, &entry_.cmd_line_args_); + entry_.cmd_line_args_ = + SplitString(data, delimiters, KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); // |data| starts with the full executable path followed by a null character. // We search for the first instance of '\0' and extract everything before it
diff --git a/process/process_linux.cc b/process/process_linux.cc index 88a310e..6e10dd2 100644 --- a/process/process_linux.cc +++ b/process/process_linux.cc
@@ -10,6 +10,7 @@ #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/logging.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" @@ -119,7 +120,7 @@ #if defined(OS_CHROMEOS) if (cgroups.Get().enabled) { - std::string pid = StringPrintf("%d", process_); + std::string pid = IntToString(process_); const base::FilePath file = background ? cgroups.Get().background_file : cgroups.Get().foreground_file;
diff --git a/process/process_metrics.h b/process/process_metrics.h index 5916b94..8b4ec86 100644 --- a/process/process_metrics.h +++ b/process/process_metrics.h
@@ -140,7 +140,8 @@ // memory usage as per definition of CommittedBytes. void GetCommittedKBytes(CommittedKBytes* usage) const; // Fills a WorkingSetKBytes containing resident private and shared memory - // usage in bytes, as per definition of WorkingSetBytes. + // usage in bytes, as per definition of WorkingSetBytes. Note that this + // function is somewhat expensive on Windows (a few ms per process). bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const; #if defined(OS_MACOSX)
diff --git a/process/process_metrics_linux.cc b/process/process_metrics_linux.cc index c564a67..0da1ec3 100644 --- a/process/process_metrics_linux.cc +++ b/process/process_metrics_linux.cc
@@ -563,17 +563,15 @@ // MemTotal value meminfo->total = 0; - std::vector<std::string> meminfo_lines; - Tokenize(meminfo_data, "\n", &meminfo_lines); - for (std::vector<std::string>::iterator it = meminfo_lines.begin(); - it != meminfo_lines.end(); ++it) { - std::vector<std::string> tokens; - SplitStringAlongWhitespace(*it, &tokens); + for (const StringPiece& line : SplitStringPiece( + meminfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) { + std::vector<StringPiece> tokens = SplitStringPiece( + line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); // HugePages_* only has a number and no suffix so we can't rely on // there being exactly 3 tokens. if (tokens.size() <= 1) { DLOG(WARNING) << "meminfo: tokens: " << tokens.size() - << " malformed line: " << *it; + << " malformed line: " << line.as_string(); continue; } @@ -630,12 +628,10 @@ // We iterate through the whole file because the position of the // fields are dependent on the kernel version and configuration. - std::vector<std::string> vmstat_lines; - Tokenize(vmstat_data, "\n", &vmstat_lines); - for (std::vector<std::string>::iterator it = vmstat_lines.begin(); - it != vmstat_lines.end(); ++it) { - std::vector<std::string> tokens; - SplitString(*it, ' ', &tokens); + for (const StringPiece& line : SplitStringPiece( + vmstat_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) { + std::vector<StringPiece> tokens = + SplitStringPiece(line, " ", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); if (tokens.size() != 2) continue; @@ -792,9 +788,9 @@ return false; } - std::vector<std::string> diskinfo_lines; - size_t line_count = Tokenize(diskinfo_data, "\n", &diskinfo_lines); - if (line_count == 0) { + std::vector<StringPiece> diskinfo_lines = SplitStringPiece( + diskinfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); + if (diskinfo_lines.size() == 0) { DLOG(WARNING) << "No lines found"; return false; } @@ -823,12 +819,12 @@ uint64 io_time = 0; uint64 weighted_io_time = 0; - for (size_t i = 0; i < line_count; i++) { - std::vector<std::string> disk_fields; - SplitStringAlongWhitespace(diskinfo_lines[i], &disk_fields); + for (const StringPiece& line : diskinfo_lines) { + std::vector<StringPiece> disk_fields = SplitStringPiece( + line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); // Fields may have overflowed and reset to zero. - if (IsValidDiskName(disk_fields[kDiskDriveName])) { + if (IsValidDiskName(disk_fields[kDiskDriveName].as_string())) { StringToUint64(disk_fields[kDiskReads], &reads); StringToUint64(disk_fields[kDiskReadsMerged], &reads_merged); StringToUint64(disk_fields[kDiskSectorsRead], §ors_read);
diff --git a/process/process_metrics_win.cc b/process/process_metrics_win.cc index 6c424e3..170f6dc 100644 --- a/process/process_metrics_win.cc +++ b/process/process_metrics_win.cc
@@ -260,7 +260,7 @@ GetProcAddress(psapi_dll, "GetPerformanceInfo")); if (!GetPerformanceInfo_func) { - // The function could be loaded! + // The function could not be loaded! memset(pPerformanceInformation, 0, cb); return FALSE; }
diff --git a/process/process_posix.cc b/process/process_posix.cc index 47b0d5b..b6f22c1 100644 --- a/process/process_posix.cc +++ b/process/process_posix.cc
@@ -295,9 +295,10 @@ #if !defined(OS_NACL_NONSFI) bool Process::Terminate(int exit_code, bool wait) const { - // result_code isn't supportable. + // exit_code isn't supportable. DCHECK(IsValid()); - DCHECK_GT(process_, 1); + CHECK_GT(process_, 0); + bool result = kill(process_, SIGTERM) == 0; if (result && wait) { int tries = 60;
diff --git a/process/process_util_unittest.cc b/process/process_util_unittest.cc index 1f7f1b2..6c1a3f1 100644 --- a/process/process_util_unittest.cc +++ b/process/process_util_unittest.cc
@@ -64,6 +64,12 @@ namespace { +const char kSignalFileSlow[] = "SlowChildProcess.die"; +const char kSignalFileKill[] = "KilledChildProcess.die"; + +#if defined(OS_POSIX) +const char kSignalFileTerm[] = "TerminatedChildProcess.die"; + #if defined(OS_ANDROID) const char kShellPath[] = "/system/bin/sh"; const char kPosixShell[] = "sh"; @@ -71,13 +77,7 @@ const char kShellPath[] = "/bin/sh"; const char kPosixShell[] = "bash"; #endif - -const char kSignalFileSlow[] = "SlowChildProcess.die"; -const char kSignalFileKill[] = "KilledChildProcess.die"; - -#if defined(OS_POSIX) -const char kSignalFileTerm[] = "TerminatedChildProcess.die"; -#endif +#endif // defined(OS_POSIX) #if defined(OS_WIN) const int kExpectedStillRunningExitCode = 0x102; @@ -1025,6 +1025,7 @@ return kSuccess; } +#if defined(CLONE_NEWUSER) && defined(CLONE_NEWPID) TEST_F(ProcessUtilTest, CloneFlags) { if (RunningOnValgrind() || !base::PathExists(FilePath("/proc/self/ns/user")) || @@ -1043,6 +1044,7 @@ EXPECT_TRUE(process.WaitForExit(&exit_code)); EXPECT_EQ(kSuccess, exit_code); } +#endif TEST(ForkWithFlagsTest, UpdatesPidCache) { // The libc clone function, which allows ForkWithFlags to keep the pid cache
diff --git a/process/process_win.cc b/process/process_win.cc index 2ad72c7..30cd9dc 100644 --- a/process/process_win.cc +++ b/process/process_win.cc
@@ -9,6 +9,7 @@ #include "base/metrics/field_trial.h" #include "base/numerics/safe_conversions.h" #include "base/process/kill.h" +#include "base/strings/string_util.h" #include "base/win/windows_version.h" namespace { @@ -189,8 +190,10 @@ DWORD background_priority = IDLE_PRIORITY_CLASS; base::FieldTrial* trial = base::FieldTrialList::Find("BackgroundRendererProcesses"); - if (trial && trial->group_name() == "AllowBelowNormalFromBrowser") + if (trial && StartsWith(trial->group_name(), "AllowBelowNormalFromBrowser", + CompareCase::SENSITIVE)) { background_priority = BELOW_NORMAL_PRIORITY_CLASS; + } priority = value ? background_priority : NORMAL_PRIORITY_CLASS; }
diff --git a/profiler/stack_sampling_profiler_win.cc b/profiler/stack_sampling_profiler_win.cc index 1ccd134..73b8e11 100644 --- a/profiler/stack_sampling_profiler_win.cc +++ b/profiler/stack_sampling_profiler_win.cc
@@ -40,7 +40,7 @@ instruction_pointers[i] = reinterpret_cast<const void*>(context->Rip); if (runtime_function) { - KNONVOLATILE_CONTEXT_POINTERS nvcontext = {0}; + KNONVOLATILE_CONTEXT_POINTERS nvcontext = {}; void* handler_data; ULONG64 establisher_frame; RtlVirtualUnwind(0, image_base, context->Rip, runtime_function, context,
diff --git a/strings/pattern.cc b/strings/pattern.cc new file mode 100644 index 0000000..56915fe --- /dev/null +++ b/strings/pattern.cc
@@ -0,0 +1,171 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/pattern.h" + +#include "base/third_party/icu/icu_utf.h" + +namespace base { + +namespace { + +static bool IsWildcard(base_icu::UChar32 character) { + return character == '*' || character == '?'; +} + +// Move the strings pointers to the point where they start to differ. +template <typename CHAR, typename NEXT> +static void EatSameChars(const CHAR** pattern, + const CHAR* pattern_end, + const CHAR** string, + const CHAR* string_end, + NEXT next) { + const CHAR* escape = NULL; + while (*pattern != pattern_end && *string != string_end) { + if (!escape && IsWildcard(**pattern)) { + // We don't want to match wildcard here, except if it's escaped. + return; + } + + // Check if the escapement char is found. If so, skip it and move to the + // next character. + if (!escape && **pattern == '\\') { + escape = *pattern; + next(pattern, pattern_end); + continue; + } + + // Check if the chars match, if so, increment the ptrs. + const CHAR* pattern_next = *pattern; + const CHAR* string_next = *string; + base_icu::UChar32 pattern_char = next(&pattern_next, pattern_end); + if (pattern_char == next(&string_next, string_end) && + pattern_char != CBU_SENTINEL) { + *pattern = pattern_next; + *string = string_next; + } else { + // Uh oh, it did not match, we are done. If the last char was an + // escapement, that means that it was an error to advance the ptr here, + // let's put it back where it was. This also mean that the MatchPattern + // function will return false because if we can't match an escape char + // here, then no one will. + if (escape) { + *pattern = escape; + } + return; + } + + escape = NULL; + } +} + +template <typename CHAR, typename NEXT> +static void EatWildcard(const CHAR** pattern, const CHAR* end, NEXT next) { + while (*pattern != end) { + if (!IsWildcard(**pattern)) + return; + next(pattern, end); + } +} + +template <typename CHAR, typename NEXT> +static bool MatchPatternT(const CHAR* eval, + const CHAR* eval_end, + const CHAR* pattern, + const CHAR* pattern_end, + int depth, + NEXT next) { + const int kMaxDepth = 16; + if (depth > kMaxDepth) + return false; + + // Eat all the matching chars. + EatSameChars(&pattern, pattern_end, &eval, eval_end, next); + + // If the string is empty, then the pattern must be empty too, or contains + // only wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + return pattern == pattern_end; + } + + // Pattern is empty but not string, this is not a match. + if (pattern == pattern_end) + return false; + + // If this is a question mark, then we need to compare the rest with + // the current string or the string with one character eaten. + const CHAR* next_pattern = pattern; + next(&next_pattern, pattern_end); + if (pattern[0] == '?') { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, depth + 1, + next)) + return true; + const CHAR* next_eval = eval; + next(&next_eval, eval_end); + if (MatchPatternT(next_eval, eval_end, next_pattern, pattern_end, depth + 1, + next)) + return true; + } + + // This is a *, try to match all the possible substrings with the remainder + // of the pattern. + if (pattern[0] == '*') { + // Collapse duplicate wild cards (********** into *) so that the + // method does not recurse unnecessarily. http://crbug.com/52839 + EatWildcard(&next_pattern, pattern_end, next); + + while (eval != eval_end) { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, depth + 1, + next)) + return true; + eval++; + } + + // We reached the end of the string, let see if the pattern contains only + // wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + if (pattern != pattern_end) + return false; + return true; + } + } + + return false; +} + +struct NextCharUTF8 { + base_icu::UChar32 operator()(const char** p, const char* end) { + base_icu::UChar32 c; + int offset = 0; + CBU8_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +struct NextCharUTF16 { + base_icu::UChar32 operator()(const char16** p, const char16* end) { + base_icu::UChar32 c; + int offset = 0; + CBU16_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +} // namespace + +bool MatchPattern(const StringPiece& eval, const StringPiece& pattern) { + return MatchPatternT(eval.data(), eval.data() + eval.size(), pattern.data(), + pattern.data() + pattern.size(), 0, NextCharUTF8()); +} + +bool MatchPattern(const StringPiece16& eval, const StringPiece16& pattern) { + return MatchPatternT(eval.data(), eval.data() + eval.size(), pattern.data(), + pattern.data() + pattern.size(), 0, NextCharUTF16()); +} + +} // namespace base
diff --git a/strings/pattern.h b/strings/pattern.h new file mode 100644 index 0000000..b698207 --- /dev/null +++ b/strings/pattern.h
@@ -0,0 +1,26 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_PATTERN_H_ +#define BASE_STRINGS_PATTERN_H_ + +#include "base/base_export.h" +#include "base/strings/string_piece.h" + +namespace base { + +// Returns true if the string passed in matches the pattern. The pattern +// string can contain wildcards like * and ? +// +// The backslash character (\) is an escape character for * and ? +// We limit the patterns to having a max of 16 * or ? characters. +// ? matches 0 or 1 character, while * matches 0 or more characters. +BASE_EXPORT bool MatchPattern(const StringPiece& string, + const StringPiece& pattern); +BASE_EXPORT bool MatchPattern(const StringPiece16& string, + const StringPiece16& pattern); + +} // namespace base + +#endif // BASE_STRINGS_PATTERN_H_
diff --git a/strings/pattern_unittest.cc b/strings/pattern_unittest.cc new file mode 100644 index 0000000..affd1d2 --- /dev/null +++ b/strings/pattern_unittest.cc
@@ -0,0 +1,50 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/pattern.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(StringUtilTest, MatchPatternTest) { + EXPECT_TRUE(MatchPattern("www.google.com", "*.com")); + EXPECT_TRUE(MatchPattern("www.google.com", "*")); + EXPECT_FALSE(MatchPattern("www.google.com", "www*.g*.org")); + EXPECT_TRUE(MatchPattern("Hello", "H?l?o")); + EXPECT_FALSE(MatchPattern("www.google.com", "http://*)")); + EXPECT_FALSE(MatchPattern("www.msn.com", "*.COM")); + EXPECT_TRUE(MatchPattern("Hello*1234", "He??o\\*1*")); + EXPECT_FALSE(MatchPattern("", "*.*")); + EXPECT_TRUE(MatchPattern("", "*")); + EXPECT_TRUE(MatchPattern("", "?")); + EXPECT_TRUE(MatchPattern("", "")); + EXPECT_FALSE(MatchPattern("Hello", "")); + EXPECT_TRUE(MatchPattern("Hello*", "Hello*")); + // Stop after a certain recursion depth. + EXPECT_FALSE(MatchPattern("123456789012345678", "?????????????????*")); + + // Test UTF8 matching. + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0", "*\xe2\x99\xa0")); + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0.", "heart: ?.")); + EXPECT_TRUE(MatchPattern("hearts: \xe2\x99\xa0\xe2\x99\xa0", "*")); + // Invalid sequences should be handled as a single invalid character. + EXPECT_TRUE(MatchPattern("invalid: \xef\xbf\xbe", "invalid: ?")); + // If the pattern has invalid characters, it shouldn't match anything. + EXPECT_FALSE(MatchPattern("\xf4\x90\x80\x80", "\xf4\x90\x80\x80")); + + // Test UTF16 character matching. + EXPECT_TRUE( + MatchPattern(UTF8ToUTF16("www.google.com"), UTF8ToUTF16("*.com"))); + EXPECT_TRUE( + MatchPattern(UTF8ToUTF16("Hello*1234"), UTF8ToUTF16("He??o\\*1*"))); + + // This test verifies that consecutive wild cards are collapsed into 1 + // wildcard (when this doesn't occur, MatchPattern reaches it's maximum + // recursion depth). + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello"), + UTF8ToUTF16("He********************************o"))); +} + +} // namespace base
diff --git a/strings/string_number_conversions.cc b/strings/string_number_conversions.cc index 642d24e..b6b65d2 100644 --- a/strings/string_number_conversions.cc +++ b/strings/string_number_conversions.cc
@@ -42,7 +42,12 @@ template <typename INT2, typename UINT2> struct ToUnsignedT<INT2, UINT2, true> { static UINT2 ToUnsigned(INT2 value) { - return static_cast<UINT2>(value < 0 ? -value : value); + if (value >= 0) { + return value; + } else { + // Avoid integer overflow when negating INT_MIN. + return static_cast<UINT2>(-(value + 1)) + 1; + } } }; @@ -131,10 +136,10 @@ return BaseCharToDigit<CHAR, BASE, BASE <= 10>::Convert(c, digit); } -// There is an IsWhitespace for wchars defined in string_util.h, but it is -// locale independent, whereas the functions we are replacing were -// locale-dependent. TBD what is desired, but for the moment let's not introduce -// a change in behaviour. +// There is an IsUnicodeWhitespace for wchars defined in string_util.h, but it +// is locale independent, whereas the functions we are replacing were +// locale-dependent. TBD what is desired, but for the moment let's not +// introduce a change in behaviour. template<typename CHAR> class WhitespaceHelper { };
diff --git a/strings/string_piece.h b/strings/string_piece.h index 349018b..a83b7d8 100644 --- a/strings/string_piece.h +++ b/strings/string_piece.h
@@ -18,12 +18,6 @@ // Both of these have the same lifetime semantics. Passing by value // generates slightly smaller code. For more discussion, Googlers can see // the thread go/stringpiecebyvalue on c-users. -// -// StringPiece16 is similar to StringPiece but for base::string16 instead of -// std::string. We do not define as large of a subset of the STL functions -// from basic_string as in StringPiece, but this can be changed if these -// functions (find, find_first_of, etc.) are found to be useful in this context. -// #ifndef BASE_STRINGS_STRING_PIECE_H_ #define BASE_STRINGS_STRING_PIECE_H_
diff --git a/strings/string_split.cc b/strings/string_split.cc index 88a6236..8998c81 100644 --- a/strings/string_split.cc +++ b/strings/string_split.cc
@@ -12,26 +12,93 @@ namespace { -template <typename STR> -void SplitStringT(const STR& str, - const typename STR::value_type s, - bool trim_whitespace, - std::vector<STR>* r) { - r->clear(); - size_t last = 0; - size_t c = str.size(); - for (size_t i = 0; i <= c; ++i) { - if (i == c || str[i] == s) { - STR tmp(str, last, i - last); - if (trim_whitespace) - TrimWhitespace(tmp, TRIM_ALL, &tmp); - // Avoid converting an empty or all-whitespace source string into a vector - // of one empty string. - if (i != c || !r->empty() || !tmp.empty()) - r->push_back(tmp); - last = i + 1; +// PieceToOutputType converts a StringPiece as needed to a given output type, +// which is either the same type of StringPiece (a NOP) or the corresponding +// non-piece string type. +// +// The default converter is a NOP, it works when the OutputType is the +// correct StringPiece. +template <typename Str, typename OutputType> +OutputType PieceToOutputType(BasicStringPiece<Str> piece) { + return piece; +} +template <> // Convert StringPiece to std::string +std::string PieceToOutputType<std::string, std::string>(StringPiece piece) { + return piece.as_string(); +} +template <> // Convert StringPiece16 to string16. +string16 PieceToOutputType<string16, string16>(StringPiece16 piece) { + return piece.as_string(); +} + +// Returns either the ASCII or UTF-16 whitespace. +template <typename Str> +BasicStringPiece<Str> WhitespaceForType(); +template <> +StringPiece16 WhitespaceForType<string16>() { + return kWhitespaceUTF16; +} +template <> +StringPiece WhitespaceForType<std::string>() { + return kWhitespaceASCII; +} + +// Optimize the single-character case to call find() on the string instead, +// since this is the common case and can be made faster. This could have been +// done with template specialization too, but would have been less clear. +// +// There is no corresponding FindFirstNotOf because StringPiece already +// implements these different versions that do the optimized searching. +size_t FindFirstOf(StringPiece piece, char c, size_t pos) { + return piece.find(c, pos); +} +size_t FindFirstOf(StringPiece16 piece, char16 c, size_t pos) { + return piece.find(c, pos); +} +size_t FindFirstOf(StringPiece piece, StringPiece one_of, size_t pos) { + return piece.find_first_of(one_of, pos); +} +size_t FindFirstOf(StringPiece16 piece, StringPiece16 one_of, size_t pos) { + return piece.find_first_of(one_of, pos); +} + +// General string splitter template. Can take 8- or 16-bit input, can produce +// the corresponding string or StringPiece output, and can take single- or +// multiple-character delimiters. +// +// DelimiterType is either a character (Str::value_type) or a string piece of +// multiple characters (BasicStringPiece<Str>). StringPiece has a version of +// find for both of these cases, and the single-character version is the most +// common and can be implemented faster, which is why this is a template. +template <typename Str, typename OutputStringType, typename DelimiterType> +static std::vector<OutputStringType> SplitStringT(BasicStringPiece<Str> str, + DelimiterType delimiter, + WhitespaceHandling whitespace, + SplitResult result_type) { + std::vector<OutputStringType> result; + if (str.empty()) + return result; + + size_t start = 0; + while (start != Str::npos) { + size_t end = FindFirstOf(str, delimiter, start); + + BasicStringPiece<Str> piece; + if (end == Str::npos) { + piece = str.substr(start); + start = Str::npos; + } else { + piece = str.substr(start, end - start); + start = end + 1; } + + if (whitespace == TRIM_WHITESPACE) + piece = TrimString(piece, WhitespaceForType<Str>(), TRIM_ALL); + + if (result_type == SPLIT_WANT_ALL || !piece.empty()) + result.push_back(PieceToOutputType<Str, OutputStringType>(piece)); } + return result; } bool SplitStringIntoKeyValue(const std::string& line, @@ -62,8 +129,8 @@ template <typename STR> void SplitStringUsingSubstrT(const STR& str, - const STR& s, - std::vector<STR>* r) { + const STR& s, + std::vector<STR>* r) { r->clear(); typename STR::size_type begin_index = 0; while (true) { @@ -83,64 +150,86 @@ } } -template<typename STR> -void SplitStringAlongWhitespaceT(const STR& str, std::vector<STR>* result) { - result->clear(); - const size_t length = str.length(); - if (!length) - return; - - bool last_was_ws = false; - size_t last_non_ws_start = 0; - for (size_t i = 0; i < length; ++i) { - switch (str[i]) { - // HTML 5 defines whitespace as: space, tab, LF, line tab, FF, or CR. - case L' ': - case L'\t': - case L'\xA': - case L'\xB': - case L'\xC': - case L'\xD': - if (!last_was_ws) { - if (i > 0) { - result->push_back( - str.substr(last_non_ws_start, i - last_non_ws_start)); - } - last_was_ws = true; - } - break; - - default: // Not a space character. - if (last_was_ws) { - last_was_ws = false; - last_non_ws_start = i; - } - break; - } - } - if (!last_was_ws) { - result->push_back( - str.substr(last_non_ws_start, length - last_non_ws_start)); - } -} - } // namespace -void SplitString(const string16& str, - char16 c, - std::vector<string16>* r) { +std::vector<std::string> SplitString(StringPiece input, + StringPiece separators, + WhitespaceHandling whitespace, + SplitResult result_type) { + if (separators.size() == 1) { + return SplitStringT<std::string, std::string, char>( + input, separators[0], whitespace, result_type); + } + return SplitStringT<std::string, std::string, StringPiece>( + input, separators, whitespace, result_type); +} + +std::vector<string16> SplitString(StringPiece16 input, + StringPiece16 separators, + WhitespaceHandling whitespace, + SplitResult result_type) { + if (separators.size() == 1) { + return SplitStringT<string16, string16, char16>(input, separators[0], + whitespace, result_type); + } + return SplitStringT<string16, string16, StringPiece16>( + input, separators, whitespace, result_type); +} + +std::vector<StringPiece> SplitStringPiece(StringPiece input, + StringPiece separators, + WhitespaceHandling whitespace, + SplitResult result_type) { + if (separators.size() == 1) { + return SplitStringT<std::string, StringPiece, char>( + input, separators[0], whitespace, result_type); + } + return SplitStringT<std::string, StringPiece, StringPiece>( + input, separators, whitespace, result_type); +} + +std::vector<StringPiece16> SplitStringPiece(StringPiece16 input, + StringPiece16 separators, + WhitespaceHandling whitespace, + SplitResult result_type) { + if (separators.size() == 1) { + return SplitStringT<string16, StringPiece16, char16>( + input, separators[0], whitespace, result_type); + } + return SplitStringT<string16, StringPiece16, StringPiece16>( + input, separators, whitespace, result_type); +} + +void SplitString(const string16& str, char16 c, std::vector<string16>* result) { DCHECK(CBU16_IS_SINGLE(c)); - SplitStringT(str, c, true, r); + *result = SplitStringT<string16, string16, char16>(str, c, TRIM_WHITESPACE, + SPLIT_WANT_ALL); + + // Backward-compat hack: The old SplitString implementation would keep + // empty substrings, for example: + // "a,,b" -> ["a", "", "b"] + // "a, ,b" -> ["a", "", "b"] + // which the current code also does. But the old one would discard them when + // the only result was that empty string: + // " " -> [] + // In the latter case, our new code will give [""] + if (result->size() == 1 && (*result)[0].empty()) + result->clear(); } void SplitString(const std::string& str, char c, - std::vector<std::string>* r) { + std::vector<std::string>* result) { #if CHAR_MIN < 0 DCHECK_GE(c, 0); #endif DCHECK_LT(c, 0x7F); - SplitStringT(str, c, true, r); + *result = SplitStringT<std::string, std::string, char>( + str, c, TRIM_WHITESPACE, SPLIT_WANT_ALL); + + // Backward-compat hack, see above. + if (result->size() == 1 && (*result)[0].empty()) + result->clear(); } bool SplitStringIntoKeyValuePairs(const std::string& line, @@ -182,31 +271,36 @@ SplitStringUsingSubstrT(str, s, r); } -void SplitStringDontTrim(const string16& str, +void SplitStringDontTrim(StringPiece16 str, char16 c, - std::vector<string16>* r) { + std::vector<string16>* result) { DCHECK(CBU16_IS_SINGLE(c)); - SplitStringT(str, c, false, r); + *result = SplitStringT<string16, string16, char16>(str, c, KEEP_WHITESPACE, + SPLIT_WANT_ALL); } -void SplitStringDontTrim(const std::string& str, +void SplitStringDontTrim(StringPiece str, char c, - std::vector<std::string>* r) { + std::vector<std::string>* result) { #if CHAR_MIN < 0 DCHECK_GE(c, 0); #endif DCHECK_LT(c, 0x7F); - SplitStringT(str, c, false, r); + *result = SplitStringT<std::string, std::string, char>( + str, c, KEEP_WHITESPACE, SPLIT_WANT_ALL); } void SplitStringAlongWhitespace(const string16& str, std::vector<string16>* result) { - SplitStringAlongWhitespaceT(str, result); + *result = SplitStringT<string16, string16, StringPiece16>( + str, StringPiece16(kWhitespaceASCIIAs16), TRIM_WHITESPACE, + SPLIT_WANT_NONEMPTY); } void SplitStringAlongWhitespace(const std::string& str, std::vector<std::string>* result) { - SplitStringAlongWhitespaceT(str, result); + *result = SplitStringT<std::string, std::string, StringPiece>( + str, StringPiece(kWhitespaceASCII), TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); } } // namespace base
diff --git a/strings/string_split.h b/strings/string_split.h index 55d8cb3..1f20571 100644 --- a/strings/string_split.h +++ b/strings/string_split.h
@@ -11,9 +11,100 @@ #include "base/base_export.h" #include "base/strings/string16.h" +#include "base/strings/string_piece.h" namespace base { +enum WhitespaceHandling { + KEEP_WHITESPACE, + TRIM_WHITESPACE, +}; + +enum SplitResult { + // Strictly return all results. + // + // If the input is ",," and the separator is ',' this will return a + // vector of three empty strings. + SPLIT_WANT_ALL, + + // Only nonempty results will be added to the results. Multiple separators + // will be coalesced. Separators at the beginning and end of the input will + // be ignored. With TRIM_WHITESPACE, whitespace-only results will be dropped. + // + // If the input is ",," and the separator is ',', this will return an empty + // vector. + SPLIT_WANT_NONEMPTY, +}; + +// Split the given string on ANY of the given separators, returning copies of +// the result. +// +// To split on either commas or semicolons, keeping all whitespace: +// +// std::vector<std::string> tokens = base::SplitString( +// input, ",;", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); +BASE_EXPORT std::vector<std::string> SplitString(StringPiece input, + StringPiece separators, + WhitespaceHandling whitespace, + SplitResult result_type); +BASE_EXPORT std::vector<string16> SplitString(StringPiece16 input, + StringPiece16 separators, + WhitespaceHandling whitespace, + SplitResult result_type); + +// Like SplitString above except it returns a vector of StringPieces which +// reference the original buffer without copying. Although you have to be +// careful to keep the original string unmodified, this provides an efficient +// way to iterate through tokens in a string. +// +// To iterate through all whitespace-separated tokens in an input string: +// +// for (const auto& cur : +// base::SplitStringPiece(input, base::kWhitespaceASCII, +// base::KEEP_WHITESPACE, +// base::SPLIT_WANT_NONEMPTY)) { +// ... +BASE_EXPORT std::vector<StringPiece> SplitStringPiece( + StringPiece input, + StringPiece separators, + WhitespaceHandling whitespace, + SplitResult result_type); +BASE_EXPORT std::vector<StringPiece16> SplitStringPiece( + StringPiece16 input, + StringPiece16 separators, + WhitespaceHandling whitespace, + SplitResult result_type); + +using StringPairs = std::vector<std::pair<std::string, std::string>>; + +// Splits |line| into key value pairs according to the given delimiters and +// removes whitespace leading each key and trailing each value. Returns true +// only if each pair has a non-empty key and value. |key_value_pairs| will +// include ("","") pairs for entries without |key_value_delimiter|. +BASE_EXPORT bool SplitStringIntoKeyValuePairs(const std::string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + StringPairs* key_value_pairs); + +// Similar to SplitString, but use a substring delimiter instead of a list of +// characters that are all possible delimiters. +// +// TODO(brettw) this should probably be changed and expanded to provide a +// mirror of the SplitString[Piece] API above, just with the different +// delimiter handling. +BASE_EXPORT void SplitStringUsingSubstr(const string16& str, + const string16& s, + std::vector<string16>* r); +BASE_EXPORT void SplitStringUsingSubstr(const std::string& str, + const std::string& s, + std::vector<std::string>* r); + +// ----------------------------------------------------------------------------- +// Backwards-compat wrappers +// +// New code should use one of the more general variants above. +// TODO(brettw) remove these and convert to the versions above. + // Splits |str| into a vector of strings delimited by |c|, placing the results // in |r|. If several instances of |c| are contiguous, or if |str| begins with // or ends with |c|, then an empty string is inserted. @@ -32,46 +123,29 @@ char c, std::vector<std::string>* r); -typedef std::vector<std::pair<std::string, std::string> > StringPairs; - -// Splits |line| into key value pairs according to the given delimiters and -// removes whitespace leading each key and trailing each value. Returns true -// only if each pair has a non-empty key and value. |key_value_pairs| will -// include ("","") pairs for entries without |key_value_delimiter|. -BASE_EXPORT bool SplitStringIntoKeyValuePairs(const std::string& line, - char key_value_delimiter, - char key_value_pair_delimiter, - StringPairs* key_value_pairs); - -// The same as SplitString, but use a substring delimiter instead of a char. -BASE_EXPORT void SplitStringUsingSubstr(const string16& str, - const string16& s, - std::vector<string16>* r); -BASE_EXPORT void SplitStringUsingSubstr(const std::string& str, - const std::string& s, - std::vector<std::string>* r); - // The same as SplitString, but don't trim white space. // NOTE: |c| must be in BMP (Basic Multilingual Plane) -BASE_EXPORT void SplitStringDontTrim(const string16& str, +BASE_EXPORT void SplitStringDontTrim(StringPiece16 str, char16 c, std::vector<string16>* r); // |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which // the trailing byte of a multi-byte character can be in the ASCII range. // UTF-8, and other single/multi-byte ASCII-compatible encodings are OK. // Note: |c| must be in the ASCII range. -BASE_EXPORT void SplitStringDontTrim(const std::string& str, +BASE_EXPORT void SplitStringDontTrim(StringPiece str, char c, - std::vector<std::string>* r); + std::vector<std::string>* result); -// 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. +// WARNING: this uses whitespace as defined by the HTML5 spec (ASCII whitespace +// only). // -// 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. +// The difference between this and calling SplitString with the whitespace +// characters as separators is the treatment of the first element when the +// string starts with whitespace. +// +// Input SplitString SplitStringAlongWhitespace +// -------------------------------------------------------- +// " a " "", "a" "a" BASE_EXPORT void SplitStringAlongWhitespace(const string16& str, std::vector<string16>* result); BASE_EXPORT void SplitStringAlongWhitespace(const std::string& str,
diff --git a/strings/string_split_unittest.cc b/strings/string_split_unittest.cc index 32bbe28..c745ab5 100644 --- a/strings/string_split_unittest.cc +++ b/strings/string_split_unittest.cc
@@ -169,7 +169,81 @@ EXPECT_THAT(results, ElementsAre("")); } -TEST(StringUtilTest, SplitString) { +TEST(StringUtilTest, SplitString_Basics) { + std::vector<std::string> r; + + r = SplitString(std::string(), ",:;", KEEP_WHITESPACE, SPLIT_WANT_ALL); + EXPECT_TRUE(r.empty()); + + // Empty separator list + r = SplitString("hello, world", "", KEEP_WHITESPACE, SPLIT_WANT_ALL); + ASSERT_EQ(1u, r.size()); + EXPECT_EQ("hello, world", r[0]); + + // Should split on any of the separators. + r = SplitString("::,,;;", ",:;", KEEP_WHITESPACE, SPLIT_WANT_ALL); + ASSERT_EQ(7u, r.size()); + for (auto str : r) + ASSERT_TRUE(str.empty()); + + r = SplitString("red, green; blue:", ",:;", TRIM_WHITESPACE, + SPLIT_WANT_NONEMPTY); + ASSERT_EQ(3u, r.size()); + EXPECT_EQ("red", r[0]); + EXPECT_EQ("green", r[1]); + EXPECT_EQ("blue", r[2]); + + // Want to split a string along whitespace sequences. + r = SplitString(" red green \tblue\n", " \t\n", TRIM_WHITESPACE, + SPLIT_WANT_NONEMPTY); + ASSERT_EQ(3u, r.size()); + EXPECT_EQ("red", r[0]); + EXPECT_EQ("green", r[1]); + EXPECT_EQ("blue", r[2]); + + // Weird case of splitting on spaces but not trimming. + r = SplitString(" red ", " ", TRIM_WHITESPACE, SPLIT_WANT_ALL); + ASSERT_EQ(3u, r.size()); + EXPECT_EQ("", r[0]); // Before the first space. + EXPECT_EQ("red", r[1]); + EXPECT_EQ("", r[2]); // After the last space. +} + +TEST(StringUtilTest, SplitString_WhitespaceAndResultType) { + std::vector<std::string> r; + + // Empty input handling. + r = SplitString(std::string(), ",", KEEP_WHITESPACE, SPLIT_WANT_ALL); + EXPECT_TRUE(r.empty()); + r = SplitString(std::string(), ",", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); + EXPECT_TRUE(r.empty()); + + // Input string is space and we're trimming. + r = SplitString(" ", ",", TRIM_WHITESPACE, SPLIT_WANT_ALL); + ASSERT_EQ(1u, r.size()); + EXPECT_EQ("", r[0]); + r = SplitString(" ", ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); + EXPECT_TRUE(r.empty()); + + // Test all 4 combinations of flags on ", ,". + r = SplitString(", ,", ",", KEEP_WHITESPACE, SPLIT_WANT_ALL); + ASSERT_EQ(3u, r.size()); + EXPECT_EQ("", r[0]); + EXPECT_EQ(" ", r[1]); + EXPECT_EQ("", r[2]); + r = SplitString(", ,", ",", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY); + ASSERT_EQ(1u, r.size()); + ASSERT_EQ(" ", r[0]); + r = SplitString(", ,", ",", TRIM_WHITESPACE, SPLIT_WANT_ALL); + ASSERT_EQ(3u, r.size()); + EXPECT_EQ("", r[0]); + EXPECT_EQ("", r[1]); + EXPECT_EQ("", r[2]); + r = SplitString(", ,", ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); + ASSERT_TRUE(r.empty()); +} + +TEST(StringUtilTest, SplitString_Legacy) { std::vector<std::wstring> r; SplitString(std::wstring(), L',', &r); @@ -197,6 +271,13 @@ EXPECT_EQ(r[2], L"c"); r.clear(); + SplitString(L"a, ,c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"c"); + r.clear(); + SplitString(L" ", L'*', &r); EXPECT_EQ(0U, r.size()); r.clear();
diff --git a/strings/string_util.cc b/strings/string_util.cc index cc77693..3317740 100644 --- a/strings/string_util.cc +++ b/strings/string_util.cc
@@ -21,14 +21,13 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/memory/singleton.h" +#include "base/strings/string_split.h" #include "base/strings/utf_string_conversion_utils.h" #include "base/strings/utf_string_conversions.h" #include "base/third_party/icu/icu_utf.h" #include "build/build_config.h" -// Remove when this entire file is in the base namespace. -using base::char16; -using base::string16; +namespace base { namespace { @@ -79,14 +78,16 @@ } template<size_t size, typename CharacterType> struct NonASCIIMask; -template<> struct NonASCIIMask<4, base::char16> { - static inline uint32_t value() { return 0xFF80FF80U; } +template <> +struct NonASCIIMask<4, char16> { + static inline uint32_t value() { return 0xFF80FF80U; } }; template<> struct NonASCIIMask<4, char> { static inline uint32_t value() { return 0x80808080U; } }; -template<> struct NonASCIIMask<8, base::char16> { - static inline uint64_t value() { return 0xFF80FF80FF80FF80ULL; } +template <> +struct NonASCIIMask<8, char16> { + static inline uint64_t value() { return 0xFF80FF80FF80FF80ULL; } }; template<> struct NonASCIIMask<8, char> { static inline uint64_t value() { return 0x8080808080808080ULL; } @@ -100,9 +101,17 @@ }; #endif // WCHAR_T_IS_UTF32 -} // namespace +// DO NOT USE. http://crbug.com/24917 +// +// tolower() will given incorrect results for non-ASCII characters. Use the +// ASCII version, base::i18n::ToLower, or base::i18n::FoldCase. This is here +// for backwards-compat for StartsWith until such calls can be updated. +struct CaseInsensitiveCompareDeprecated { + public: + bool operator()(char16 x, char16 y) const { return tolower(x) == tolower(y); } +}; -namespace base { +} // namespace bool IsWprintfFormatPortable(const wchar_t* format) { for (const wchar_t* position = format; *position != '\0'; ++position) { @@ -139,6 +148,53 @@ return true; } +template <class StringType> +int CompareCaseInsensitiveASCIIT(BasicStringPiece<StringType> a, + BasicStringPiece<StringType> b) { + // Find the first characters that aren't equal and compare them. If the end + // of one of the strings is found before a nonequal character, the lengths + // of the strings are compared. + size_t i = 0; + while (i < a.length() && i < b.length()) { + typename StringType::value_type lower_a = ToLowerASCII(a[i]); + typename StringType::value_type lower_b = ToLowerASCII(b[i]); + if (lower_a < lower_b) + return -1; + if (lower_a > lower_b) + return 1; + i++; + } + + // End of one string hit before finding a different character. Expect the + // common case to be "strings equal" at this point so check that first. + if (a.length() == b.length()) + return 0; + + if (a.length() < b.length()) + return -1; + return 1; +} + +int CompareCaseInsensitiveASCII(StringPiece a, StringPiece b) { + return CompareCaseInsensitiveASCIIT<std::string>(a, b); +} + +int CompareCaseInsensitiveASCII(StringPiece16 a, StringPiece16 b) { + return CompareCaseInsensitiveASCIIT<string16>(a, b); +} + +bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b) { + if (a.length() != b.length()) + return false; + return CompareCaseInsensitiveASCIIT<std::string>(a, b) == 0; +} + +bool EqualsCaseInsensitiveASCII(StringPiece16 a, StringPiece16 b) { + if (a.length() != b.length()) + return false; + return CompareCaseInsensitiveASCIIT<string16>(a, b) == 0; +} + const std::string& EmptyString() { return EmptyStrings::GetInstance()->s; } @@ -168,54 +224,60 @@ } bool ReplaceChars(const string16& input, - const base::StringPiece16& replace_chars, + const StringPiece16& replace_chars, const string16& replace_with, string16* output) { return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output); } bool ReplaceChars(const std::string& input, - const base::StringPiece& replace_chars, + const StringPiece& replace_chars, const std::string& replace_with, std::string* output) { return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output); } bool RemoveChars(const string16& input, - const base::StringPiece16& remove_chars, + const StringPiece16& remove_chars, string16* output) { return ReplaceChars(input, remove_chars.as_string(), string16(), output); } bool RemoveChars(const std::string& input, - const base::StringPiece& remove_chars, + const StringPiece& remove_chars, std::string* output) { return ReplaceChars(input, remove_chars.as_string(), std::string(), output); } -template<typename STR> -TrimPositions TrimStringT(const STR& input, - const STR& trim_chars, +template <typename Str> +TrimPositions TrimStringT(const Str& input, + BasicStringPiece<Str> trim_chars, TrimPositions positions, - STR* output) { - // Find the edges of leading/trailing whitespace as desired. + Str* output) { + // Find the edges of leading/trailing whitespace as desired. Need to use + // a StringPiece version of input to be able to call find* on it with the + // StringPiece version of trim_chars (normally the trim_chars will be a + // constant so avoid making a copy). + BasicStringPiece<Str> input_piece(input); const size_t last_char = input.length() - 1; - const size_t first_good_char = (positions & TRIM_LEADING) ? - input.find_first_not_of(trim_chars) : 0; - const size_t last_good_char = (positions & TRIM_TRAILING) ? - input.find_last_not_of(trim_chars) : last_char; + const size_t first_good_char = (positions & TRIM_LEADING) + ? input_piece.find_first_not_of(trim_chars) + : 0; + const size_t last_good_char = (positions & TRIM_TRAILING) + ? input_piece.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)) { + // When the string was all trimmed, report that we stripped off characters + // from whichever position the caller was interested in. For empty input, we + // stripped no characters, 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. + // Trim. *output = input.substr(first_good_char, last_good_char - first_good_char + 1); @@ -226,17 +288,39 @@ } bool TrimString(const string16& input, - const base::StringPiece16& trim_chars, + StringPiece16 trim_chars, string16* output) { - return TrimStringT(input, trim_chars.as_string(), TRIM_ALL, output) != - TRIM_NONE; + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; } bool TrimString(const std::string& input, - const base::StringPiece& trim_chars, + StringPiece trim_chars, std::string* output) { - return TrimStringT(input, trim_chars.as_string(), TRIM_ALL, output) != - TRIM_NONE; + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; +} + +template <typename Str> +BasicStringPiece<Str> TrimStringPieceT(BasicStringPiece<Str> input, + BasicStringPiece<Str> trim_chars, + TrimPositions positions) { + size_t begin = + (positions & TRIM_LEADING) ? input.find_first_not_of(trim_chars) : 0; + size_t end = (positions & TRIM_TRAILING) + ? input.find_last_not_of(trim_chars) + 1 + : input.size(); + return input.substr(begin, end - begin); +} + +StringPiece16 TrimString(StringPiece16 input, + const StringPiece16& trim_chars, + TrimPositions positions) { + return TrimStringPieceT(input, trim_chars, positions); +} + +StringPiece TrimString(StringPiece input, + const StringPiece& trim_chars, + TrimPositions positions) { + return TrimStringPieceT(input, trim_chars, positions); } void TruncateUTF8ToByteSize(const std::string& input, @@ -278,14 +362,22 @@ TrimPositions TrimWhitespace(const string16& input, TrimPositions positions, string16* output) { - return TrimStringT(input, base::string16(kWhitespaceUTF16), positions, - output); + return TrimStringT(input, StringPiece16(kWhitespaceUTF16), positions, output); +} + +StringPiece16 TrimWhitespaceASCII(StringPiece16 input, + TrimPositions positions) { + return TrimStringPieceT(input, StringPiece16(kWhitespaceUTF16), positions); } TrimPositions TrimWhitespaceASCII(const std::string& input, TrimPositions positions, std::string* output) { - return TrimStringT(input, std::string(kWhitespaceASCII), positions, output); + return TrimStringT(input, StringPiece(kWhitespaceASCII), positions, output); +} + +StringPiece TrimWhitespaceASCII(StringPiece input, TrimPositions positions) { + return TrimStringPieceT(input, StringPiece(kWhitespaceASCII), positions); } // This function is only for backward-compatibility. @@ -309,7 +401,7 @@ int chars_written = 0; for (typename STR::const_iterator i(text.begin()); i != text.end(); ++i) { - if (IsWhitespace(*i)) { + if (IsUnicodeWhitespace(*i)) { if (!in_whitespace) { // Reduce all whitespace sequences to a single space. in_whitespace = true; @@ -482,55 +574,123 @@ return std::equal(b.begin(), b.end(), a.begin()); } -} // namespace base +template <typename Str> +bool StartsWithT(BasicStringPiece<Str> str, + BasicStringPiece<Str> search_for, + CompareCase case_sensitivity) { + if (search_for.size() > str.size()) + return false; -bool StartsWithASCII(const std::string& str, - const std::string& search, - bool case_sensitive) { - if (case_sensitive) - return str.compare(0, search.length(), search) == 0; - else - return base::strncasecmp(str.c_str(), search.c_str(), search.length()) == 0; -} + BasicStringPiece<Str> source = str.substr(0, search_for.size()); -template <typename STR> -bool StartsWithT(const STR& str, const STR& search, bool case_sensitive) { - if (case_sensitive) { - return str.compare(0, search.length(), search) == 0; - } else { - if (search.size() > str.size()) + switch (case_sensitivity) { + case CompareCase::SENSITIVE: + return source == search_for; + + case CompareCase::INSENSITIVE_ASCII: + return std::equal( + search_for.begin(), search_for.end(), source.begin(), + CaseInsensitiveCompareASCII<typename Str::value_type>()); + + default: + NOTREACHED(); return false; - return std::equal(search.begin(), search.end(), str.begin(), - base::CaseInsensitiveCompare<typename STR::value_type>()); } } -bool StartsWith(const string16& str, const string16& search, +bool StartsWith(StringPiece str, + StringPiece search_for, + CompareCase case_sensitivity) { + return StartsWithT<std::string>(str, search_for, case_sensitivity); +} + +bool StartsWith(StringPiece16 str, + StringPiece16 search_for, + CompareCase case_sensitivity) { + return StartsWithT<string16>(str, search_for, case_sensitivity); +} + +bool StartsWith(const string16& str, + const string16& search, bool case_sensitive) { - return StartsWithT(str, search, case_sensitive); + if (!case_sensitive) { + // This function was originally written using the current locale functions + // for case-insensitive comparisons. Emulate this behavior until callers + // can be converted either to use the case-insensitive ASCII one (most + // callers) or ICU functions in base_i18n. + if (search.size() > str.size()) + return false; + return std::equal(search.begin(), search.end(), str.begin(), + CaseInsensitiveCompareDeprecated()); + } + return StartsWith(StringPiece16(str), StringPiece16(search), + CompareCase::SENSITIVE); } -template <typename STR> -bool EndsWithT(const STR& str, const STR& search, bool case_sensitive) { - size_t str_length = str.length(); - size_t search_length = search.length(); - if (search_length > str_length) +template <typename Str> +bool EndsWithT(BasicStringPiece<Str> str, + BasicStringPiece<Str> search_for, + CompareCase case_sensitivity) { + if (search_for.size() > str.size()) return false; - if (case_sensitive) - return str.compare(str_length - search_length, search_length, search) == 0; - return std::equal(search.begin(), search.end(), - str.begin() + (str_length - search_length), - base::CaseInsensitiveCompare<typename STR::value_type>()); + + BasicStringPiece<Str> source = + str.substr(str.size() - search_for.size(), search_for.size()); + + switch (case_sensitivity) { + case CompareCase::SENSITIVE: + return source == search_for; + + case CompareCase::INSENSITIVE_ASCII: + return std::equal( + source.begin(), source.end(), search_for.begin(), + CaseInsensitiveCompareASCII<typename Str::value_type>()); + + default: + NOTREACHED(); + return false; + } } -bool EndsWith(const std::string& str, const std::string& search, - bool case_sensitive) { - return EndsWithT(str, search, case_sensitive); +bool EndsWith(StringPiece str, + StringPiece search_for, + CompareCase case_sensitivity) { + return EndsWithT<std::string>(str, search_for, case_sensitivity); } -bool EndsWith(const string16& str, const string16& search, +bool EndsWith(StringPiece16 str, + StringPiece16 search_for, + CompareCase case_sensitivity) { + return EndsWithT<string16>(str, search_for, case_sensitivity); +} + +bool EndsWith(const string16& str, + const string16& search, bool case_sensitive) { - return EndsWithT(str, search, case_sensitive); + if (!case_sensitive) { + // This function was originally written using the current locale functions + // for case-insensitive comparisons. Emulate this behavior until callers + // can be converted either to use the case-insensitive ASCII one (most + // callers) or ICU functions in base_i18n. + if (search.size() > str.size()) + return false; + return std::equal(search.begin(), search.end(), + str.begin() + (str.size() - search.size()), + CaseInsensitiveCompareDeprecated()); + } + return EndsWith(StringPiece16(str), StringPiece16(search), + CompareCase::SENSITIVE); +} + +char HexDigitToInt(wchar_t c) { + DCHECK(IsHexDigit(c)); + if (c >= '0' && c <= '9') + return static_cast<char>(c - '0'); + if (c >= 'A' && c <= 'F') + return static_cast<char>(c - 'A' + 10); + if (c >= 'a' && c <= 'f') + return static_cast<char>(c - 'a' + 10); + return 0; } static const char* const kByteStringsUnlocalized[] = { @@ -561,20 +721,20 @@ kByteStringsUnlocalized[dimension]); } - return base::ASCIIToUTF16(buf); + return ASCIIToUTF16(buf); } // Runs in O(n) time in the length of |str|. -template<class StringType> +template <class StringType> void DoReplaceSubstringsAfterOffset(StringType* str, size_t offset, - const StringType& find_this, - const StringType& replace_with, + BasicStringPiece<StringType> find_this, + BasicStringPiece<StringType> replace_with, bool replace_all) { DCHECK(!find_this.empty()); // If the find string doesn't appear, there's nothing to do. - offset = str->find(find_this, offset); + offset = str->find(find_this.data(), offset, find_this.size()); if (offset == StringType::npos) return; @@ -582,7 +742,7 @@ // complicated. size_t find_length = find_this.length(); if (!replace_all) { - str->replace(offset, find_length, replace_with); + str->replace(offset, find_length, replace_with.data(), replace_with.size()); return; } @@ -591,8 +751,10 @@ size_t replace_length = replace_with.length(); if (find_length == replace_length) { do { - str->replace(offset, find_length, replace_with); - offset = str->find(find_this, offset + replace_length); + str->replace(offset, find_length, replace_with.data(), + replace_with.size()); + offset = str->find(find_this.data(), offset + replace_length, + find_this.size()); } while (offset != StringType::npos); return; } @@ -609,11 +771,14 @@ size_t write_offset = offset; do { if (replace_length) { - str->replace(write_offset, replace_length, replace_with); + str->replace(write_offset, replace_length, replace_with.data(), + replace_with.size()); write_offset += replace_length; } size_t read_offset = offset + find_length; - offset = std::min(str->find(find_this, read_offset), str_length); + offset = + std::min(str->find(find_this.data(), read_offset, find_this.size()), + str_length); size_t length = offset - read_offset; if (length) { memmove(&(*str)[write_offset], &(*str)[read_offset], @@ -642,13 +807,15 @@ // exit from the loop, |current_match| will point at the last instance of // the find string, and we won't need to find() it again immediately. current_match = offset; - offset = str->find(find_this, offset + find_length); + offset = + str->find(find_this.data(), offset + find_length, find_this.size()); } while (offset != StringType::npos); str->resize(final_length); // Now do the replacement loop, working backwards through the string. - for (size_t prev_match = str_length, write_offset = final_length; ; - current_match = str->rfind(find_this, current_match - 1)) { + for (size_t prev_match = str_length, write_offset = final_length;; + current_match = + str->rfind(find_this.data(), current_match - 1, find_this.size())) { size_t read_offset = current_match + find_length; size_t length = prev_match - read_offset; if (length) { @@ -657,7 +824,8 @@ length * sizeof(typename StringType::value_type)); } write_offset -= replace_length; - str->replace(write_offset, replace_length, replace_with); + str->replace(write_offset, replace_length, replace_with.data(), + replace_with.size()); if (current_match == first_match) return; prev_match = current_match; @@ -666,128 +834,97 @@ void ReplaceFirstSubstringAfterOffset(string16* str, size_t start_offset, - const string16& find_this, - const string16& replace_with) { - DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, - false); // replace first instance + StringPiece16 find_this, + StringPiece16 replace_with) { + DoReplaceSubstringsAfterOffset<string16>( + str, start_offset, find_this, replace_with, false); // Replace first. } void ReplaceFirstSubstringAfterOffset(std::string* str, size_t start_offset, - const std::string& find_this, - const std::string& replace_with) { - DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, - false); // replace first instance + StringPiece find_this, + StringPiece replace_with) { + DoReplaceSubstringsAfterOffset<std::string>( + str, start_offset, find_this, replace_with, false); // Replace first. } void ReplaceSubstringsAfterOffset(string16* str, size_t start_offset, - const string16& find_this, - const string16& replace_with) { - DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, - true); // replace all instances + StringPiece16 find_this, + StringPiece16 replace_with) { + DoReplaceSubstringsAfterOffset<string16>(str, start_offset, find_this, + replace_with, true); // Replace all. } void ReplaceSubstringsAfterOffset(std::string* str, size_t start_offset, - const std::string& find_this, - const std::string& replace_with) { - DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, - true); // replace all instances + StringPiece find_this, + StringPiece replace_with) { + DoReplaceSubstringsAfterOffset<std::string>( + str, start_offset, find_this, replace_with, true); // Replace all. } - -template<typename STR> -static size_t TokenizeT(const STR& str, - const STR& delimiters, - std::vector<STR>* tokens) { - tokens->clear(); - - size_t start = str.find_first_not_of(delimiters); - while (start != STR::npos) { - size_t end = str.find_first_of(delimiters, start + 1); - if (end == STR::npos) { - tokens->push_back(str.substr(start)); - break; - } else { - tokens->push_back(str.substr(start, end - start)); - start = str.find_first_not_of(delimiters, end + 1); - } - } - - return tokens->size(); +template <class string_type> +inline typename string_type::value_type* WriteIntoT(string_type* str, + size_t length_with_null) { + DCHECK_GT(length_with_null, 1u); + str->reserve(length_with_null); + str->resize(length_with_null - 1); + return &((*str)[0]); } -size_t Tokenize(const string16& str, - const string16& delimiters, - std::vector<string16>* tokens) { - return TokenizeT(str, delimiters, tokens); +char* WriteInto(std::string* str, size_t length_with_null) { + return WriteIntoT(str, length_with_null); } -size_t Tokenize(const std::string& str, - const std::string& delimiters, - std::vector<std::string>* tokens) { - return TokenizeT(str, delimiters, tokens); +char16* WriteInto(string16* str, size_t length_with_null) { + return WriteIntoT(str, length_with_null); } -size_t Tokenize(const base::StringPiece& str, - const base::StringPiece& delimiters, - std::vector<base::StringPiece>* tokens) { - return TokenizeT(str, delimiters, tokens); -} - -template<typename STR> -static STR JoinStringT(const std::vector<STR>& parts, const STR& sep) { +template <typename STR> +static STR JoinStringT(const std::vector<STR>& parts, + BasicStringPiece<STR> sep) { if (parts.empty()) return STR(); STR result(parts[0]); - typename std::vector<STR>::const_iterator iter = parts.begin(); + auto iter = parts.begin(); ++iter; for (; iter != parts.end(); ++iter) { - result += sep; + sep.AppendToString(&result); result += *iter; } return result; } -std::string JoinString(const std::vector<std::string>& parts, char sep) { - return JoinStringT(parts, std::string(1, sep)); -} - -string16 JoinString(const std::vector<string16>& parts, char16 sep) { - return JoinStringT(parts, string16(1, sep)); -} - std::string JoinString(const std::vector<std::string>& parts, - const std::string& separator) { + StringPiece separator) { return JoinStringT(parts, separator); } string16 JoinString(const std::vector<string16>& parts, - const string16& separator) { + StringPiece16 separator) { return JoinStringT(parts, separator); } -template<class FormatStringType, class OutStringType> -OutStringType DoReplaceStringPlaceholders(const FormatStringType& format_string, - const std::vector<OutStringType>& subst, std::vector<size_t>* offsets) { +template <class FormatStringType, class OutStringType> +OutStringType DoReplaceStringPlaceholders( + const FormatStringType& format_string, + const std::vector<OutStringType>& subst, + std::vector<size_t>* offsets) { size_t substitutions = subst.size(); size_t sub_length = 0; - for (typename std::vector<OutStringType>::const_iterator iter = subst.begin(); - iter != subst.end(); ++iter) { - sub_length += iter->length(); - } + for (const auto& cur : subst) + sub_length += cur.length(); OutStringType formatted; formatted.reserve(format_string.length() + sub_length); std::vector<ReplacementOffset> r_offsets; - for (typename FormatStringType::const_iterator i = format_string.begin(); - i != format_string.end(); ++i) { + for (auto i = format_string.begin(); i != format_string.end(); ++i) { if ('$' == *i) { if (i + 1 != format_string.end()) { ++i; @@ -825,10 +962,8 @@ } } if (offsets) { - for (std::vector<ReplacementOffset>::const_iterator i = r_offsets.begin(); - i != r_offsets.end(); ++i) { - offsets->push_back(i->offset); - } + for (const auto& cur : r_offsets) + offsets->push_back(cur.offset); } return formatted; } @@ -839,7 +974,7 @@ return DoReplaceStringPlaceholders(format_string, subst, offsets); } -std::string ReplaceStringPlaceholders(const base::StringPiece& format_string, +std::string ReplaceStringPlaceholders(const StringPiece& format_string, const std::vector<std::string>& subst, std::vector<size_t>* offsets) { return DoReplaceStringPlaceholders(format_string, subst, offsets); @@ -859,161 +994,6 @@ return result; } -static bool IsWildcard(base_icu::UChar32 character) { - return character == '*' || character == '?'; -} - -// Move the strings pointers to the point where they start to differ. -template <typename CHAR, typename NEXT> -static void EatSameChars(const CHAR** pattern, const CHAR* pattern_end, - const CHAR** string, const CHAR* string_end, - NEXT next) { - const CHAR* escape = NULL; - while (*pattern != pattern_end && *string != string_end) { - if (!escape && IsWildcard(**pattern)) { - // We don't want to match wildcard here, except if it's escaped. - return; - } - - // Check if the escapement char is found. If so, skip it and move to the - // next character. - if (!escape && **pattern == '\\') { - escape = *pattern; - next(pattern, pattern_end); - continue; - } - - // Check if the chars match, if so, increment the ptrs. - const CHAR* pattern_next = *pattern; - const CHAR* string_next = *string; - base_icu::UChar32 pattern_char = next(&pattern_next, pattern_end); - if (pattern_char == next(&string_next, string_end) && - pattern_char != CBU_SENTINEL) { - *pattern = pattern_next; - *string = string_next; - } else { - // Uh oh, it did not match, we are done. If the last char was an - // escapement, that means that it was an error to advance the ptr here, - // let's put it back where it was. This also mean that the MatchPattern - // function will return false because if we can't match an escape char - // here, then no one will. - if (escape) { - *pattern = escape; - } - return; - } - - escape = NULL; - } -} - -template <typename CHAR, typename NEXT> -static void EatWildcard(const CHAR** pattern, const CHAR* end, NEXT next) { - while (*pattern != end) { - if (!IsWildcard(**pattern)) - return; - next(pattern, end); - } -} - -template <typename CHAR, typename NEXT> -static bool MatchPatternT(const CHAR* eval, const CHAR* eval_end, - const CHAR* pattern, const CHAR* pattern_end, - int depth, - NEXT next) { - const int kMaxDepth = 16; - if (depth > kMaxDepth) - return false; - - // Eat all the matching chars. - EatSameChars(&pattern, pattern_end, &eval, eval_end, next); - - // If the string is empty, then the pattern must be empty too, or contains - // only wildcards. - if (eval == eval_end) { - EatWildcard(&pattern, pattern_end, next); - return pattern == pattern_end; - } - - // Pattern is empty but not string, this is not a match. - if (pattern == pattern_end) - return false; - - // If this is a question mark, then we need to compare the rest with - // the current string or the string with one character eaten. - const CHAR* next_pattern = pattern; - next(&next_pattern, pattern_end); - if (pattern[0] == '?') { - if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, - depth + 1, next)) - return true; - const CHAR* next_eval = eval; - next(&next_eval, eval_end); - if (MatchPatternT(next_eval, eval_end, next_pattern, pattern_end, - depth + 1, next)) - return true; - } - - // This is a *, try to match all the possible substrings with the remainder - // of the pattern. - if (pattern[0] == '*') { - // Collapse duplicate wild cards (********** into *) so that the - // method does not recurse unnecessarily. http://crbug.com/52839 - EatWildcard(&next_pattern, pattern_end, next); - - while (eval != eval_end) { - if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, - depth + 1, next)) - return true; - eval++; - } - - // We reached the end of the string, let see if the pattern contains only - // wildcards. - if (eval == eval_end) { - EatWildcard(&pattern, pattern_end, next); - if (pattern != pattern_end) - return false; - return true; - } - } - - return false; -} - -struct NextCharUTF8 { - base_icu::UChar32 operator()(const char** p, const char* end) { - base_icu::UChar32 c; - int offset = 0; - CBU8_NEXT(*p, offset, end - *p, c); - *p += offset; - return c; - } -}; - -struct NextCharUTF16 { - base_icu::UChar32 operator()(const char16** p, const char16* end) { - base_icu::UChar32 c; - int offset = 0; - CBU16_NEXT(*p, offset, end - *p, c); - *p += offset; - return c; - } -}; - -bool MatchPattern(const base::StringPiece& eval, - const base::StringPiece& pattern) { - return MatchPatternT(eval.data(), eval.data() + eval.size(), - pattern.data(), pattern.data() + pattern.size(), - 0, NextCharUTF8()); -} - -bool MatchPattern(const string16& eval, const string16& pattern) { - return MatchPatternT(eval.c_str(), eval.c_str() + eval.size(), - pattern.c_str(), pattern.c_str() + pattern.size(), - 0, NextCharUTF16()); -} - // The following code is compatible with the OpenBSD lcpy interface. See: // http://www.gratisoft.us/todd/papers/strlcpy.html // ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c @@ -1038,9 +1018,11 @@ } // namespace -size_t base::strlcpy(char* dst, const char* src, size_t dst_size) { +size_t strlcpy(char* dst, const char* src, size_t dst_size) { return lcpyT<char>(dst, src, dst_size); } -size_t base::wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size) { +size_t wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size) { return lcpyT<wchar_t>(dst, src, dst_size); } + +} // namespace base
diff --git a/strings/string_util.h b/strings/string_util.h index bea44ae..9e50a33 100644 --- a/strings/string_util.h +++ b/strings/string_util.h
@@ -21,23 +21,10 @@ namespace base { -// C standard-library functions like "strncasecmp" and "snprintf" that aren't -// cross-platform are provided as "base::strncasecmp", and their prototypes -// are listed below. These functions are then implemented as inline calls -// to the platform-specific equivalents in the platform-specific headers. - -// Compares the two strings s1 and s2 without regard to case using -// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if -// s2 > s1 according to a lexicographic comparison. -int strcasecmp(const char* s1, const char* s2); - -// Compares up to count characters of s1 and s2 without regard to case using -// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if -// s2 > s1 according to a lexicographic comparison. -int strncasecmp(const char* s1, const char* s2, size_t count); - -// Same as strncmp but for char16 strings. -int strncmp16(const char16* s1, const char16* s2, size_t count); +// C standard-library functions that aren't cross-platform are provided as +// "base::...", and their prototypes are listed below. These functions are +// then implemented as inline calls to the platform-specific equivalents in the +// platform-specific headers. // Wrapper for vsnprintf that always null-terminates and always returns the // number of characters that would be in an untruncated formatted @@ -59,6 +46,19 @@ return result; } +// TODO(mark) http://crbug.com/472900 crashpad shouldn't use base while +// being DEPSed in. This backwards-compat hack is provided until crashpad is +// updated. +#if defined(OS_WIN) +inline int strcasecmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +#else // Posix +inline int strcasecmp(const char* string1, const char* string2) { + return ::strcasecmp(string1, string2); +} +#endif + // BSD-style safe and consistent string copy functions. // Copies |src| to |dst|, where |dst_size| is the total allocated size of |dst|. // Copies at most |dst_size|-1 characters, and always NULL terminates |dst|, as @@ -103,17 +103,14 @@ return (c >= 'a' && c <= 'z') ? (c + ('A' - 'a')) : c; } -// Function objects to aid in comparing/searching strings. - -template<typename Char> struct CaseInsensitiveCompare { - public: - bool operator()(Char x, Char y) const { - // TODO(darin): Do we really want to do locale sensitive comparisons here? - // See http://crbug.com/24917 - return tolower(x) == tolower(y); - } -}; - +// Functor for case-insensitive ASCII comparisons for STL algorithms like +// std::search. +// +// Note that a full Unicode version of this functor is not possible to write +// because case mappings might change the number of characters, depend on +// context (combining accents), and require handling UTF-16. If you need +// proper Unicode support, use base::i18n::ToLower/FoldCase and then just +// use a normal operator== on the result. template<typename Char> struct CaseInsensitiveCompareASCII { public: bool operator()(Char x, Char y) const { @@ -121,6 +118,22 @@ } }; +// Like strcasecmp for case-insensitive ASCII characters only. Returns: +// -1 (a < b) +// 0 (a == b) +// 1 (a > b) +// (unlike strcasecmp which can return values greater or less than 1/-1). For +// full Unicode support, use base::i18n::ToLower or base::i18h::FoldCase +// and then just call the normal string operators on the result. +BASE_EXPORT int CompareCaseInsensitiveASCII(StringPiece a, StringPiece b); +BASE_EXPORT int CompareCaseInsensitiveASCII(StringPiece16 a, StringPiece16 b); + +// Equality for ASCII case-insensitive comparisons. For full Unicode support, +// use base::i18n::ToLower or base::i18h::FoldCase and then compare with either +// == or !=. +BASE_EXPORT bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b); +BASE_EXPORT bool EqualsCaseInsensitiveASCII(StringPiece16 a, StringPiece16 b); + // These threadsafe functions return references to globally unique empty // strings. // @@ -138,10 +151,12 @@ BASE_EXPORT const string16& EmptyString16(); // Contains the set of characters representing whitespace in the corresponding -// encoding. Null-terminated. -BASE_EXPORT extern const wchar_t kWhitespaceWide[]; -BASE_EXPORT extern const char16 kWhitespaceUTF16[]; +// encoding. Null-terminated. The ASCII versions are the whitespaces as defined +// by HTML5, and don't include control characters. +BASE_EXPORT extern const wchar_t kWhitespaceWide[]; // Includes Unicode. +BASE_EXPORT extern const char16 kWhitespaceUTF16[]; // Includes Unicode. BASE_EXPORT extern const char kWhitespaceASCII[]; +BASE_EXPORT extern const char16 kWhitespaceASCIIAs16[]; // No unicode. // Null-terminated string representing the UTF-8 byte order mark. BASE_EXPORT extern const char kUtf8ByteOrderMark[]; @@ -150,10 +165,10 @@ // if any characters were removed. |remove_chars| must be null-terminated. // NOTE: Safe to use the same variable for both |input| and |output|. BASE_EXPORT bool RemoveChars(const string16& input, - const base::StringPiece16& remove_chars, + const StringPiece16& remove_chars, string16* output); BASE_EXPORT bool RemoveChars(const std::string& input, - const base::StringPiece& remove_chars, + const StringPiece& remove_chars, std::string* output); // Replaces characters in |replace_chars| from anywhere in |input| with @@ -162,49 +177,65 @@ // |replace_chars| must be null-terminated. // NOTE: Safe to use the same variable for both |input| and |output|. BASE_EXPORT bool ReplaceChars(const string16& input, - const base::StringPiece16& replace_chars, + const StringPiece16& replace_chars, const string16& replace_with, string16* output); BASE_EXPORT bool ReplaceChars(const std::string& input, - const base::StringPiece& replace_chars, + const StringPiece& replace_chars, const std::string& replace_with, std::string* output); +enum TrimPositions { + TRIM_NONE = 0, + TRIM_LEADING = 1 << 0, + TRIM_TRAILING = 1 << 1, + TRIM_ALL = TRIM_LEADING | TRIM_TRAILING, +}; + // Removes characters in |trim_chars| from the beginning and end of |input|. -// |trim_chars| must be null-terminated. -// NOTE: Safe to use the same variable for both |input| and |output|. +// The 8-bit version only works on 8-bit characters, not UTF-8. +// +// It is safe to use the same variable for both |input| and |output| (this is +// the normal usage to trim in-place). BASE_EXPORT bool TrimString(const string16& input, - const base::StringPiece16& trim_chars, + StringPiece16 trim_chars, string16* output); BASE_EXPORT bool TrimString(const std::string& input, - const base::StringPiece& trim_chars, + StringPiece trim_chars, std::string* output); +// StringPiece versions of the above. The returned pieces refer to the original +// buffer. +BASE_EXPORT StringPiece16 TrimString(StringPiece16 input, + const StringPiece16& trim_chars, + TrimPositions positions); +BASE_EXPORT StringPiece TrimString(StringPiece input, + const StringPiece& trim_chars, + TrimPositions positions); + // Truncates a string to the nearest UTF-8 character that will leave // the string less than or equal to the specified byte size. BASE_EXPORT void TruncateUTF8ToByteSize(const std::string& input, const size_t byte_size, std::string* output); -// Trims any whitespace from either end of the input string. Returns where -// whitespace was found. -// The non-wide version has two functions: -// * TrimWhitespaceASCII() -// This function is for ASCII strings and only looks for ASCII whitespace; -// Please choose the best one according to your usage. +// Trims any whitespace from either end of the input string. +// +// The StringPiece versions return a substring referencing the input buffer. +// The ASCII versions look only for ASCII whitespace. +// +// The std::string versions return where whitespace was found. // NOTE: Safe to use the same variable for both input and output. -enum TrimPositions { - TRIM_NONE = 0, - TRIM_LEADING = 1 << 0, - TRIM_TRAILING = 1 << 1, - TRIM_ALL = TRIM_LEADING | TRIM_TRAILING, -}; BASE_EXPORT TrimPositions TrimWhitespace(const string16& input, TrimPositions positions, - base::string16* output); + string16* output); +BASE_EXPORT StringPiece16 TrimWhitespace(StringPiece16 input, + TrimPositions positions); BASE_EXPORT TrimPositions TrimWhitespaceASCII(const std::string& input, TrimPositions positions, std::string* output); +BASE_EXPORT StringPiece TrimWhitespaceASCII(StringPiece input, + TrimPositions positions); // Deprecated. This function is only for backward compatibility and calls // TrimWhitespaceASCII(). @@ -315,32 +346,41 @@ // strings are not ASCII. BASE_EXPORT bool EqualsASCII(const string16& a, const StringPiece& b); -} // namespace base +// Indicates case sensitivity of comparisons. Only ASCII case insensitivity +// is supported. Full Unicode case-insensitive conversions would need to go in +// base/i18n so it can use ICU. +// +// If you need to do Unicode-aware case-insensitive StartsWith/EndsWith, it's +// best to call base::i18n::ToLower() or base::i18n::FoldCase() (see +// base/i18n/case_conversion.h for usage advice) on the arguments, and then use +// the results to a case-sensitive comparison. +enum class CompareCase { + SENSITIVE, + INSENSITIVE_ASCII, +}; -#if defined(OS_WIN) -#include "base/strings/string_util_win.h" -#elif defined(OS_POSIX) -#include "base/strings/string_util_posix.h" -#else -#error Define string operations appropriately for your platform -#endif +BASE_EXPORT bool StartsWith(StringPiece str, + StringPiece search_for, + CompareCase case_sensitivity); +BASE_EXPORT bool StartsWith(StringPiece16 str, + StringPiece16 search_for, + CompareCase case_sensitivity); +BASE_EXPORT bool EndsWith(StringPiece str, + StringPiece search_for, + CompareCase case_sensitivity); +BASE_EXPORT bool EndsWith(StringPiece16 str, + StringPiece16 search_for, + CompareCase case_sensitivity); -// Returns true if str starts with search, or false otherwise. -BASE_EXPORT bool StartsWithASCII(const std::string& str, - const std::string& search, - bool case_sensitive); -BASE_EXPORT bool StartsWith(const base::string16& str, - const base::string16& search, - bool case_sensitive); - -// Returns true if str ends with search, or false otherwise. -BASE_EXPORT bool EndsWith(const std::string& str, - const std::string& search, - bool case_sensitive); -BASE_EXPORT bool EndsWith(const base::string16& str, - const base::string16& search, - bool case_sensitive); - +// DEPRECATED. Returns true if str starts/ends with search, or false otherwise. +// TODO(brettw) remove in favor of the "enum" versions above. +inline bool StartsWithASCII(const std::string& str, + const std::string& search, + bool case_sensitive) { + return StartsWith( + StringPiece(str), StringPiece(search), + case_sensitive ? CompareCase::SENSITIVE : CompareCase::INSENSITIVE_ASCII); +} // Determines the type of ASCII character, independent of locale (the C // library versions will change based on locale). @@ -364,20 +404,15 @@ (c >= 'a' && c <= 'f'); } -template <typename Char> -inline char HexDigitToInt(Char c) { - DCHECK(IsHexDigit(c)); - if (c >= '0' && c <= '9') - return static_cast<char>(c - '0'); - if (c >= 'A' && c <= 'F') - return static_cast<char>(c - 'A' + 10); - if (c >= 'a' && c <= 'f') - return static_cast<char>(c - 'a' + 10); - return 0; -} +// Returns the integer corresponding to the given hex character. For example: +// '4' -> 4 +// 'a' -> 10 +// 'B' -> 11 +// Assumes the input is a valid hex character. DCHECKs in debug builds if not. +BASE_EXPORT char HexDigitToInt(wchar_t c); -// Returns true if it's a whitespace character. -inline bool IsWhitespace(wchar_t c) { +// Returns true if it's a Unicode whitespace character. +inline bool IsUnicodeWhitespace(wchar_t c) { return wcschr(base::kWhitespaceWide, c) != NULL; } @@ -385,20 +420,18 @@ // appropriate for use in any UI; use of FormatBytes and friends in ui/base is // highly recommended instead. TODO(avi): Figure out how to get callers to use // FormatBytes instead; remove this. -BASE_EXPORT base::string16 FormatBytesUnlocalized(int64 bytes); +BASE_EXPORT string16 FormatBytesUnlocalized(int64 bytes); // Starting at |start_offset| (usually 0), replace the first instance of // |find_this| with |replace_with|. -BASE_EXPORT void ReplaceFirstSubstringAfterOffset( - base::string16* str, - size_t start_offset, - const base::string16& find_this, - const base::string16& replace_with); -BASE_EXPORT void ReplaceFirstSubstringAfterOffset( - std::string* str, - size_t start_offset, - const std::string& find_this, - const std::string& replace_with); +BASE_EXPORT void ReplaceFirstSubstringAfterOffset(base::string16* str, + size_t start_offset, + StringPiece16 find_this, + StringPiece16 replace_with); +BASE_EXPORT void ReplaceFirstSubstringAfterOffset(std::string* str, + size_t start_offset, + StringPiece find_this, + StringPiece replace_with); // Starting at |start_offset| (usually 0), look through |str| and replace all // instances of |find_this| with |replace_with|. @@ -406,15 +439,14 @@ // This does entire substrings; use std::replace in <algorithm> for single // characters, for example: // std::replace(str.begin(), str.end(), 'a', 'b'); -BASE_EXPORT void ReplaceSubstringsAfterOffset( - base::string16* str, - size_t start_offset, - const base::string16& find_this, - const base::string16& replace_with); +BASE_EXPORT void ReplaceSubstringsAfterOffset(string16* str, + size_t start_offset, + StringPiece16 find_this, + StringPiece16 replace_with); BASE_EXPORT void ReplaceSubstringsAfterOffset(std::string* str, size_t start_offset, - const std::string& find_this, - const std::string& replace_with); + StringPiece find_this, + StringPiece replace_with); // Reserves enough memory in |str| to accommodate |length_with_null| characters, // sets the size of |str| to |length_with_null - 1| characters, and returns a @@ -436,72 +468,45 @@ // of the string, and not doing that will mean people who access |str| rather // than str.c_str() will get back a string of whatever size |str| had on entry // to this function (probably 0). -template <class string_type> -inline typename string_type::value_type* WriteInto(string_type* str, - size_t length_with_null) { - DCHECK_GT(length_with_null, 1u); - str->reserve(length_with_null); - str->resize(length_with_null - 1); - return &((*str)[0]); -} - -//----------------------------------------------------------------------------- - -// Splits a string into its fields delimited by any of the characters in -// |delimiters|. Each field is added to the |tokens| vector. Returns the -// number of tokens found. -BASE_EXPORT size_t Tokenize(const base::string16& str, - const base::string16& delimiters, - std::vector<base::string16>* tokens); -BASE_EXPORT size_t Tokenize(const std::string& str, - const std::string& delimiters, - std::vector<std::string>* tokens); -BASE_EXPORT size_t Tokenize(const base::StringPiece& str, - const base::StringPiece& delimiters, - std::vector<base::StringPiece>* tokens); +BASE_EXPORT char* WriteInto(std::string* str, size_t length_with_null); +BASE_EXPORT char16* WriteInto(string16* str, size_t length_with_null); +#ifndef OS_WIN +BASE_EXPORT wchar_t* WriteInto(std::wstring* str, size_t length_with_null); +#endif // Does the opposite of SplitString(). -BASE_EXPORT base::string16 JoinString(const std::vector<base::string16>& parts, - base::char16 s); -BASE_EXPORT std::string JoinString( - const std::vector<std::string>& parts, char s); - -// Join |parts| using |separator|. -BASE_EXPORT std::string JoinString( - const std::vector<std::string>& parts, - const std::string& separator); -BASE_EXPORT base::string16 JoinString( - const std::vector<base::string16>& parts, - const base::string16& separator); +BASE_EXPORT std::string JoinString(const std::vector<std::string>& parts, + StringPiece separator); +BASE_EXPORT string16 JoinString(const std::vector<string16>& parts, + StringPiece16 separator); // Replace $1-$2-$3..$9 in the format string with |a|-|b|-|c|..|i| respectively. // Additionally, any number of consecutive '$' characters is replaced by that // number less one. Eg $$->$, $$$->$$, etc. The offsets parameter here can be // NULL. This only allows you to use up to nine replacements. -BASE_EXPORT base::string16 ReplaceStringPlaceholders( - const base::string16& format_string, - const std::vector<base::string16>& subst, - std::vector<size_t>* offsets); +BASE_EXPORT string16 +ReplaceStringPlaceholders(const string16& format_string, + const std::vector<string16>& subst, + std::vector<size_t>* offsets); BASE_EXPORT std::string ReplaceStringPlaceholders( - const base::StringPiece& format_string, + const StringPiece& format_string, const std::vector<std::string>& subst, std::vector<size_t>* offsets); // Single-string shortcut for ReplaceStringHolders. |offset| may be NULL. -BASE_EXPORT base::string16 ReplaceStringPlaceholders( - const base::string16& format_string, - const base::string16& a, - size_t* offset); +BASE_EXPORT string16 ReplaceStringPlaceholders(const string16& format_string, + const string16& a, + size_t* offset); -// Returns true if the string passed in matches the pattern. The pattern -// string can contain wildcards like * and ? -// The backslash character (\) is an escape character for * and ? -// We limit the patterns to having a max of 16 * or ? characters. -// ? matches 0 or 1 character, while * matches 0 or more characters. -BASE_EXPORT bool MatchPattern(const base::StringPiece& string, - const base::StringPiece& pattern); -BASE_EXPORT bool MatchPattern(const base::string16& string, - const base::string16& pattern); +} // namespace base + +#if defined(OS_WIN) +#include "base/strings/string_util_win.h" +#elif defined(OS_POSIX) +#include "base/strings/string_util_posix.h" +#else +#error Define string operations appropriately for your platform +#endif #endif // BASE_STRINGS_STRING_UTIL_H_
diff --git a/strings/string_util_constants.cc b/strings/string_util_constants.cc index 146e5fd..d443daa 100644 --- a/strings/string_util_constants.cc +++ b/strings/string_util_constants.cc
@@ -52,6 +52,14 @@ 0 }; +const char16 kWhitespaceASCIIAs16[] = {0x09, // CHARACTER TABULATION + 0x0A, // LINE FEED (LF) + 0x0B, // LINE TABULATION + 0x0C, // FORM FEED (FF) + 0x0D, // CARRIAGE RETURN (CR) + 0x20, // SPACE + 0}; + const char kUtf8ByteOrderMark[] = "\xEF\xBB\xBF"; } // namespace base
diff --git a/strings/string_util_posix.h b/strings/string_util_posix.h index f4009d4..9e96697 100644 --- a/strings/string_util_posix.h +++ b/strings/string_util_posix.h
@@ -20,27 +20,11 @@ return ::strdup(str); } -inline int strcasecmp(const char* string1, const char* string2) { - return ::strcasecmp(string1, string2); -} - -inline int strncasecmp(const char* string1, const char* string2, size_t count) { - return ::strncasecmp(string1, string2, count); -} - inline int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments) { return ::vsnprintf(buffer, size, format, arguments); } -inline int strncmp16(const char16* s1, const char16* s2, size_t count) { -#if defined(WCHAR_T_IS_UTF16) - return ::wcsncmp(s1, s2, count); -#elif defined(WCHAR_T_IS_UTF32) - return c16memcmp(s1, s2, count); -#endif -} - inline int vswprintf(wchar_t* buffer, size_t size, const wchar_t* format, va_list arguments) { DCHECK(IsWprintfFormatPortable(format));
diff --git a/strings/string_util_unittest.cc b/strings/string_util_unittest.cc index fb0bead..75fc58a 100644 --- a/strings/string_util_unittest.cc +++ b/strings/string_util_unittest.cc
@@ -669,128 +669,7 @@ EXPECT_EQ(15, HexDigitToInt('f')); } -// Test for Tokenize -template <typename STR> -void TokenizeTest() { - std::vector<STR> r; - size_t size; - - size = Tokenize(STR("This is a string"), STR(" "), &r); - EXPECT_EQ(4U, size); - ASSERT_EQ(4U, r.size()); - EXPECT_EQ(r[0], STR("This")); - EXPECT_EQ(r[1], STR("is")); - EXPECT_EQ(r[2], STR("a")); - EXPECT_EQ(r[3], STR("string")); - r.clear(); - - size = Tokenize(STR("one,two,three"), STR(","), &r); - EXPECT_EQ(3U, size); - ASSERT_EQ(3U, r.size()); - EXPECT_EQ(r[0], STR("one")); - EXPECT_EQ(r[1], STR("two")); - EXPECT_EQ(r[2], STR("three")); - r.clear(); - - size = Tokenize(STR("one,two:three;four"), STR(",:"), &r); - EXPECT_EQ(3U, size); - ASSERT_EQ(3U, r.size()); - EXPECT_EQ(r[0], STR("one")); - EXPECT_EQ(r[1], STR("two")); - EXPECT_EQ(r[2], STR("three;four")); - r.clear(); - - size = Tokenize(STR("one,two:three;four"), STR(";,:"), &r); - EXPECT_EQ(4U, size); - ASSERT_EQ(4U, r.size()); - EXPECT_EQ(r[0], STR("one")); - EXPECT_EQ(r[1], STR("two")); - EXPECT_EQ(r[2], STR("three")); - EXPECT_EQ(r[3], STR("four")); - r.clear(); - - size = Tokenize(STR("one, two, three"), STR(","), &r); - EXPECT_EQ(3U, size); - ASSERT_EQ(3U, r.size()); - EXPECT_EQ(r[0], STR("one")); - EXPECT_EQ(r[1], STR(" two")); - EXPECT_EQ(r[2], STR(" three")); - r.clear(); - - size = Tokenize(STR("one, two, three, "), STR(","), &r); - EXPECT_EQ(4U, size); - ASSERT_EQ(4U, r.size()); - EXPECT_EQ(r[0], STR("one")); - EXPECT_EQ(r[1], STR(" two")); - EXPECT_EQ(r[2], STR(" three")); - EXPECT_EQ(r[3], STR(" ")); - r.clear(); - - size = Tokenize(STR("one, two, three,"), STR(","), &r); - EXPECT_EQ(3U, size); - ASSERT_EQ(3U, r.size()); - EXPECT_EQ(r[0], STR("one")); - EXPECT_EQ(r[1], STR(" two")); - EXPECT_EQ(r[2], STR(" three")); - r.clear(); - - size = Tokenize(STR(), STR(","), &r); - EXPECT_EQ(0U, size); - ASSERT_EQ(0U, r.size()); - r.clear(); - - size = Tokenize(STR(","), STR(","), &r); - EXPECT_EQ(0U, size); - ASSERT_EQ(0U, r.size()); - r.clear(); - - size = Tokenize(STR(",;:."), STR(".:;,"), &r); - EXPECT_EQ(0U, size); - ASSERT_EQ(0U, r.size()); - r.clear(); - - size = Tokenize(STR("\t\ta\t"), STR("\t"), &r); - EXPECT_EQ(1U, size); - ASSERT_EQ(1U, r.size()); - EXPECT_EQ(r[0], STR("a")); - r.clear(); - - size = Tokenize(STR("\ta\t\nb\tcc"), STR("\n"), &r); - EXPECT_EQ(2U, size); - ASSERT_EQ(2U, r.size()); - EXPECT_EQ(r[0], STR("\ta\t")); - EXPECT_EQ(r[1], STR("b\tcc")); - r.clear(); -} - -TEST(StringUtilTest, TokenizeStdString) { - TokenizeTest<std::string>(); -} - -TEST(StringUtilTest, TokenizeStringPiece) { - TokenizeTest<StringPiece>(); -} - -// Test for JoinString TEST(StringUtilTest, JoinString) { - std::vector<std::string> in; - EXPECT_EQ("", JoinString(in, ',')); - - in.push_back("a"); - EXPECT_EQ("a", JoinString(in, ',')); - - in.push_back("b"); - in.push_back("c"); - EXPECT_EQ("a,b,c", JoinString(in, ',')); - - in.push_back(std::string()); - EXPECT_EQ("a,b,c,", JoinString(in, ',')); - in.push_back(" "); - EXPECT_EQ("a|b|c|| ", JoinString(in, '|')); -} - -// Test for JoinString overloaded with std::string separator -TEST(StringUtilTest, JoinStringWithString) { std::string separator(", "); std::vector<std::string> parts; EXPECT_EQ(std::string(), JoinString(parts, separator)); @@ -808,8 +687,7 @@ EXPECT_EQ("a|b|c|| ", JoinString(parts, "|")); } -// Test for JoinString overloaded with string16 separator -TEST(StringUtilTest, JoinStringWithString16) { +TEST(StringUtilTest, JoinString16) { string16 separator = ASCIIToUTF16(", "); std::vector<string16> parts; EXPECT_EQ(string16(), JoinString(parts, separator)); @@ -828,59 +706,83 @@ } TEST(StringUtilTest, StartsWith) { - EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", true)); - EXPECT_FALSE(StartsWithASCII("JavaScript:url", "javascript", true)); - EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", false)); - EXPECT_TRUE(StartsWithASCII("JavaScript:url", "javascript", false)); - EXPECT_FALSE(StartsWithASCII("java", "javascript", true)); - EXPECT_FALSE(StartsWithASCII("java", "javascript", false)); - EXPECT_FALSE(StartsWithASCII(std::string(), "javascript", false)); - EXPECT_FALSE(StartsWithASCII(std::string(), "javascript", true)); - EXPECT_TRUE(StartsWithASCII("java", std::string(), false)); - EXPECT_TRUE(StartsWithASCII("java", std::string(), true)); + EXPECT_TRUE( + StartsWith("javascript:url", "javascript", base::CompareCase::SENSITIVE)); + EXPECT_FALSE( + StartsWith("JavaScript:url", "javascript", base::CompareCase::SENSITIVE)); + EXPECT_TRUE(StartsWith("javascript:url", "javascript", + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_TRUE(StartsWith("JavaScript:url", "javascript", + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(StartsWith("java", "javascript", base::CompareCase::SENSITIVE)); + EXPECT_FALSE( + StartsWith("java", "javascript", base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(StartsWith(std::string(), "javascript", + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE( + StartsWith(std::string(), "javascript", base::CompareCase::SENSITIVE)); + EXPECT_TRUE( + StartsWith("java", std::string(), base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_TRUE(StartsWith("java", std::string(), base::CompareCase::SENSITIVE)); EXPECT_TRUE(StartsWith(ASCIIToUTF16("javascript:url"), - ASCIIToUTF16("javascript"), true)); + ASCIIToUTF16("javascript"), + base::CompareCase::SENSITIVE)); EXPECT_FALSE(StartsWith(ASCIIToUTF16("JavaScript:url"), - ASCIIToUTF16("javascript"), true)); + ASCIIToUTF16("javascript"), + base::CompareCase::SENSITIVE)); EXPECT_TRUE(StartsWith(ASCIIToUTF16("javascript:url"), - ASCIIToUTF16("javascript"), false)); + ASCIIToUTF16("javascript"), + base::CompareCase::INSENSITIVE_ASCII)); EXPECT_TRUE(StartsWith(ASCIIToUTF16("JavaScript:url"), - ASCIIToUTF16("javascript"), false)); - EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), - ASCIIToUTF16("javascript"), true)); - EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), - ASCIIToUTF16("javascript"), false)); - EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), false)); - EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), true)); - EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), false)); - EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), true)); + ASCIIToUTF16("javascript"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), ASCIIToUTF16("javascript"), + base::CompareCase::SENSITIVE)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), ASCIIToUTF16("javascript"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), + base::CompareCase::SENSITIVE)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), + base::CompareCase::SENSITIVE)); } TEST(StringUtilTest, EndsWith) { - EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), - ASCIIToUTF16(".plugin"), true)); - EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.Plugin"), - ASCIIToUTF16(".plugin"), true)); - EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), - ASCIIToUTF16(".plugin"), false)); - EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.Plugin"), - ASCIIToUTF16(".plugin"), false)); - EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), true)); - EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), false)); - EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), - ASCIIToUTF16(".plugin"), true)); - EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), - ASCIIToUTF16(".plugin"), false)); - EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), false)); - EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), true)); - EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), false)); - EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), true)); - EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), - ASCIIToUTF16(".plugin"), false)); - EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), ASCIIToUTF16(".plugin"), true)); - EXPECT_TRUE(EndsWith(string16(), string16(), false)); - EXPECT_TRUE(EndsWith(string16(), string16(), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), ASCIIToUTF16(".plugin"), + base::CompareCase::SENSITIVE)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.Plugin"), ASCIIToUTF16(".plugin"), + base::CompareCase::SENSITIVE)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), ASCIIToUTF16(".plugin"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.Plugin"), ASCIIToUTF16(".plugin"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), + base::CompareCase::SENSITIVE)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), ASCIIToUTF16(".plugin"), + base::CompareCase::SENSITIVE)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), ASCIIToUTF16(".plugin"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), + base::CompareCase::SENSITIVE)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), + base::CompareCase::SENSITIVE)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), ASCIIToUTF16(".plugin"), + base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), ASCIIToUTF16(".plugin"), + base::CompareCase::SENSITIVE)); + EXPECT_TRUE( + EndsWith(string16(), string16(), base::CompareCase::INSENSITIVE_ASCII)); + EXPECT_TRUE(EndsWith(string16(), string16(), base::CompareCase::SENSITIVE)); } TEST(StringUtilTest, GetStringFWithOffsets) { @@ -994,45 +896,6 @@ "$1 $$2 $$$3"); } -TEST(StringUtilTest, MatchPatternTest) { - EXPECT_TRUE(MatchPattern("www.google.com", "*.com")); - EXPECT_TRUE(MatchPattern("www.google.com", "*")); - EXPECT_FALSE(MatchPattern("www.google.com", "www*.g*.org")); - EXPECT_TRUE(MatchPattern("Hello", "H?l?o")); - EXPECT_FALSE(MatchPattern("www.google.com", "http://*)")); - EXPECT_FALSE(MatchPattern("www.msn.com", "*.COM")); - EXPECT_TRUE(MatchPattern("Hello*1234", "He??o\\*1*")); - EXPECT_FALSE(MatchPattern("", "*.*")); - EXPECT_TRUE(MatchPattern("", "*")); - EXPECT_TRUE(MatchPattern("", "?")); - EXPECT_TRUE(MatchPattern("", "")); - EXPECT_FALSE(MatchPattern("Hello", "")); - EXPECT_TRUE(MatchPattern("Hello*", "Hello*")); - // Stop after a certain recursion depth. - EXPECT_FALSE(MatchPattern("123456789012345678", "?????????????????*")); - - // Test UTF8 matching. - EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0", "*\xe2\x99\xa0")); - EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0.", "heart: ?.")); - EXPECT_TRUE(MatchPattern("hearts: \xe2\x99\xa0\xe2\x99\xa0", "*")); - // Invalid sequences should be handled as a single invalid character. - EXPECT_TRUE(MatchPattern("invalid: \xef\xbf\xbe", "invalid: ?")); - // If the pattern has invalid characters, it shouldn't match anything. - EXPECT_FALSE(MatchPattern("\xf4\x90\x80\x80", "\xf4\x90\x80\x80")); - - // Test UTF16 character matching. - EXPECT_TRUE(MatchPattern(UTF8ToUTF16("www.google.com"), - UTF8ToUTF16("*.com"))); - EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello*1234"), - UTF8ToUTF16("He??o\\*1*"))); - - // This test verifies that consecutive wild cards are collapsed into 1 - // wildcard (when this doesn't occur, MatchPattern reaches it's maximum - // recursion depth). - EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello"), - UTF8ToUTF16("He********************************o"))); -} - TEST(StringUtilTest, LcpyTest) { // Test the normal case where we fit in our buffer. { @@ -1197,6 +1060,26 @@ kWhitespaceUTF16)); } +TEST(StringUtilTest, CompareCaseInsensitiveASCII) { + EXPECT_EQ(0, CompareCaseInsensitiveASCII("", "")); + EXPECT_EQ(0, CompareCaseInsensitiveASCII("Asdf", "aSDf")); + + // Differing lengths. + EXPECT_EQ(-1, CompareCaseInsensitiveASCII("Asdf", "aSDfA")); + EXPECT_EQ(1, CompareCaseInsensitiveASCII("AsdfA", "aSDf")); + + // Differing values. + EXPECT_EQ(-1, CompareCaseInsensitiveASCII("AsdfA", "aSDfb")); + EXPECT_EQ(1, CompareCaseInsensitiveASCII("Asdfb", "aSDfA")); +} + +TEST(StringUtilTest, EqualsCaseInsensitiveASCII) { + EXPECT_TRUE(EqualsCaseInsensitiveASCII("", "")); + EXPECT_TRUE(EqualsCaseInsensitiveASCII("Asdf", "aSDF")); + EXPECT_FALSE(EqualsCaseInsensitiveASCII("bsdf", "aSDF")); + EXPECT_FALSE(EqualsCaseInsensitiveASCII("Asdf", "aSDFz")); +} + class WriteIntoTest : public testing::Test { protected: static void WritesCorrectly(size_t num_chars) {
diff --git a/strings/string_util_win.h b/strings/string_util_win.h index 61eda20..839a799 100644 --- a/strings/string_util_win.h +++ b/strings/string_util_win.h
@@ -20,18 +20,6 @@ return _strdup(str); } -inline int strcasecmp(const char* s1, const char* s2) { - return _stricmp(s1, s2); -} - -inline int strncasecmp(const char* s1, const char* s2, size_t count) { - return _strnicmp(s1, s2, count); -} - -inline int strncmp16(const char16* s1, const char16* s2, size_t count) { - return ::wcsncmp(s1, s2, count); -} - inline int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments) { int length = vsnprintf_s(buffer, size, size - 1, format, arguments);
diff --git a/strings/utf_string_conversions_unittest.cc b/strings/utf_string_conversions_unittest.cc index a7b12ff..6abcfc0 100644 --- a/strings/utf_string_conversions_unittest.cc +++ b/strings/utf_string_conversions_unittest.cc
@@ -185,25 +185,22 @@ #endif // defined(WCHAR_T_IS_UTF32) TEST(UTFStringConversionsTest, ConvertMultiString) { - static wchar_t wmulti[] = { - L'f', L'o', L'o', L'\0', - L'b', L'a', L'r', L'\0', - L'b', L'a', L'z', L'\0', - L'\0' - }; + static char16 multi16[] = {'f', 'o', 'o', '\0', 'b', 'a', 'r', + '\0', 'b', 'a', 'z', '\0', '\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()); + string16 multistring16; + memcpy(WriteInto(&multistring16, arraysize(multi16)), multi16, + sizeof(multi16)); + EXPECT_EQ(arraysize(multi16) - 1, multistring16.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); + const std::string& converted = UTF16ToUTF8(multistring16); EXPECT_EQ(arraysize(multi) - 1, converted.length()); EXPECT_EQ(expected, converted); }
diff --git a/synchronization/condition_variable_win.cc b/synchronization/condition_variable_win.cc index 5f165c8..470e564 100644 --- a/synchronization/condition_variable_win.cc +++ b/synchronization/condition_variable_win.cc
@@ -205,10 +205,10 @@ }; WinXPCondVar::WinXPCondVar(Lock* user_lock) - : user_lock_(*user_lock), - run_state_(RUNNING), - allocation_counter_(0), - recycling_list_size_(0) { + : run_state_(RUNNING), + user_lock_(*user_lock), + recycling_list_size_(0), + allocation_counter_(0) { DCHECK(user_lock); }
diff --git a/sys_info.cc b/sys_info.cc index 8640dc1..f24ebd3 100644 --- a/sys_info.cc +++ b/sys_info.cc
@@ -41,14 +41,14 @@ // Low End Device Mode will be enabled if this client is assigned to // one of those EnabledXXX groups. - if (StartsWithASCII(group_name, "Enabled", true)) + if (StartsWith(group_name, "Enabled", CompareCase::SENSITIVE)) return true; return g_lazy_low_end_device.Get().value(); } #endif -#if !defined(OS_MACOSX) || defined(OS_IOS) +#if (!defined(OS_MACOSX) || defined(OS_IOS)) && !defined(OS_ANDROID) std::string SysInfo::HardwareModelName() { return std::string(); }
diff --git a/sys_info.h b/sys_info.h index 654d694..5a81dc1 100644 --- a/sys_info.h +++ b/sys_info.h
@@ -52,9 +52,9 @@ static int64 Uptime(); // Returns a descriptive string for the current machine model or an empty - // string if machime model is unknown or an error occured. - // e.g. MacPro1,1 on Mac. - // Only implemented on OS X, will return an empty string on other platforms. + // string if the machine model is unknown or an error occured. + // e.g. "MacPro1,1" on Mac, or "Nexus 5" on Android. Only implemented on OS X, + // Android, and Chrome OS. This returns an empty string on other platforms. static std::string HardwareModelName(); // Returns the name of the host operating system. @@ -129,9 +129,6 @@ // Returns the Android build ID. static std::string GetAndroidBuildID(); - // Returns the device's name. - static std::string GetDeviceName(); - static int DalvikHeapSizeMB(); static int DalvikHeapGrowthLimitMB(); #endif // defined(OS_ANDROID)
diff --git a/sys_info_android.cc b/sys_info_android.cc index 245097f..c288ae2 100644 --- a/sys_info_android.cc +++ b/sys_info_android.cc
@@ -155,28 +155,16 @@ namespace base { -std::string SysInfo::OperatingSystemName() { - return "Android"; -} - -std::string SysInfo::GetAndroidBuildCodename() { - char os_version_codename_str[PROP_VALUE_MAX]; - __system_property_get("ro.build.version.codename", os_version_codename_str); - return std::string(os_version_codename_str); -} - -std::string SysInfo::GetAndroidBuildID() { - char os_build_id_str[PROP_VALUE_MAX]; - __system_property_get("ro.build.id", os_build_id_str); - return std::string(os_build_id_str); -} - -std::string SysInfo::GetDeviceName() { +std::string SysInfo::HardwareModelName() { char device_model_str[PROP_VALUE_MAX]; __system_property_get("ro.product.model", device_model_str); return std::string(device_model_str); } +std::string SysInfo::OperatingSystemName() { + return "Android"; +} + std::string SysInfo::OperatingSystemVersion() { int32 major, minor, bugfix; OperatingSystemVersionNumbers(&major, &minor, &bugfix); @@ -195,6 +183,18 @@ bugfix_version); } +std::string SysInfo::GetAndroidBuildCodename() { + char os_version_codename_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.version.codename", os_version_codename_str); + return std::string(os_version_codename_str); +} + +std::string SysInfo::GetAndroidBuildID() { + char os_build_id_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.id", os_build_id_str); + return std::string(os_build_id_str); +} + int SysInfo::DalvikHeapSizeMB() { static int heap_size = GetDalvikHeapSizeMB(); return heap_size;
diff --git a/task_runner.h b/task_runner.h index 7d07b8c..6369c4f 100644 --- a/task_runner.h +++ b/task_runner.h
@@ -104,7 +104,7 @@ // public: // void GetData() { // scoped_refptr<DataBuffer> buffer = new DataBuffer(); - // target_thread_.message_loop_proxy()->PostTaskAndReply( + // target_thread_.task_runner()->PostTaskAndReply( // FROM_HERE, // base::Bind(&DataBuffer::AddData, buffer), // base::Bind(&DataLoader::OnDataReceived, AsWeakPtr(), buffer));
diff --git a/task_runner_util.h b/task_runner_util.h index b6dd0f3..da088db 100644 --- a/task_runner_util.h +++ b/task_runner_util.h
@@ -47,7 +47,7 @@ // PostTaskAndReplyWithResult as in this example: // // PostTaskAndReplyWithResult( -// target_thread_.message_loop_proxy(), +// target_thread_.task_runner(), // FROM_HERE, // Bind(&DoWorkAndReturn), // Bind(&Callback));
diff --git a/test/BUILD.gn b/test/BUILD.gn index a8ae0cf..db0fc91 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn
@@ -128,18 +128,24 @@ "values_test_util.h", ] + data = [ + # The isolate needs this script for setting up the test. It's not actually + # needed to run this target locally. + "//testing/test_env.py", + ] + public_deps = [ ":test_config", "//base", - "//base:i18n", "//base:base_static", + "//base:i18n", ] deps = [ "//base/third_party/dynamic_annotations", "//testing/gmock", "//testing/gtest", - "//third_party/libxml", "//third_party/icu:icuuc", + "//third_party/libxml", ] if (!is_posix) {
diff --git a/test/android/java/src/org/chromium/base/TestUiThread.java b/test/android/java/src/org/chromium/base/TestUiThread.java index 77f9660..4abec80 100644 --- a/test/android/java/src/org/chromium/base/TestUiThread.java +++ b/test/android/java/src/org/chromium/base/TestUiThread.java
@@ -19,7 +19,7 @@ @ThreadSafe public class TestUiThread { private static final AtomicBoolean sStarted = new AtomicBoolean(false); - private static final String TAG = Log.makeTag("TestUiThread"); + private static final String TAG = "cr.TestUiThread"; @CalledByNative private static void loop() {
diff --git a/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java b/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java index 53dee4a..4c7f3b8 100644 --- a/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java +++ b/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java
@@ -10,17 +10,8 @@ import android.test.ActivityInstrumentationTestCase2; import android.util.Log; -import org.chromium.base.BaseChromiumApplication; -import org.chromium.base.CommandLine; import org.chromium.base.test.util.CommandLineFlags; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - /** * Base class for all Activity-based Instrumentation tests. * @@ -43,26 +34,10 @@ super(activityClass); } - /** - * Sets up the CommandLine with the appropriate flags. - * - * This will add the difference of the sets of flags specified by {@link CommandLineFlags.Add} - * and {@link CommandLineFlags.Remove} to the {@link org.chromium.base.CommandLine}. Note that - * trying to remove a flag set externally, i.e. by the command-line flags file, will not work. - */ @Override protected void setUp() throws Exception { super.setUp(); - - CommandLine.reset(); - Context targetContext = getTargetContext(); - assertNotNull("Unable to get a non-null target context.", targetContext); - - BaseChromiumApplication.initCommandLine(targetContext); - Set<String> flags = getFlags(getClass().getMethod(getName())); - for (String flag : flags) { - CommandLine.getInstance().appendSwitch(flag); - } + CommandLineFlags.setUp(getTargetContext(), getClass().getMethod(getName())); } /** @@ -88,31 +63,4 @@ } return targetContext; } - - private static Set<String> getFlags(AnnotatedElement element) { - AnnotatedElement parent = (element instanceof Method) - ? ((Method) element).getDeclaringClass() - : ((Class) element).getSuperclass(); - Set<String> flags = (parent == null) ? new HashSet<String>() : getFlags(parent); - - if (element.isAnnotationPresent(CommandLineFlags.Add.class)) { - flags.addAll( - Arrays.asList(element.getAnnotation(CommandLineFlags.Add.class).value())); - } - - if (element.isAnnotationPresent(CommandLineFlags.Remove.class)) { - List<String> flagsToRemove = - Arrays.asList(element.getAnnotation(CommandLineFlags.Remove.class).value()); - for (String flagToRemove : flagsToRemove) { - // If your test fails here, you have tried to remove a command-line flag via - // CommandLineFlags.Remove that was loaded into CommandLine via something other - // than CommandLineFlags.Add (probably the command-line flag file). - assertFalse("Unable to remove command-line flag \"" + flagToRemove + "\".", - CommandLine.getInstance().hasSwitch(flagToRemove)); - } - flags.removeAll(flagsToRemove); - } - - return flags; - } }
diff --git a/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java b/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java index 8a3395a..c90ebf2 100644 --- a/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java +++ b/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java
@@ -94,7 +94,7 @@ @Override protected TestResult createTestResult() { SkippingTestResult r = new SkippingTestResult(); - r.addSkipCheck(new MinAndroidSdkLevelSkipCheck()); + addSkipChecks(r); return r; } }; @@ -103,6 +103,13 @@ } /** + * Adds the desired SkipChecks to result. Subclasses can add additional SkipChecks. + */ + protected void addSkipChecks(SkippingTestResult result) { + result.addSkipCheck(new MinAndroidSdkLevelSkipCheck()); + } + + /** * Checks the device's SDK level against any specified minimum requirement. */ public static class MinAndroidSdkLevelSkipCheck implements SkipCheck {
diff --git a/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java b/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java index 2feb83d..4dde9e5 100644 --- a/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java +++ b/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
@@ -4,11 +4,24 @@ package org.chromium.base.test.util; +import android.content.Context; + +import junit.framework.Assert; + +import org.chromium.base.BaseChromiumApplication; +import org.chromium.base.CommandLine; + import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Provides annotations related to command-line flag handling. @@ -44,5 +57,48 @@ String[] value(); } + /** + * Sets up the CommandLine with the appropriate flags. + * + * This will add the difference of the sets of flags specified by {@link CommandLineFlags.Add} + * and {@link CommandLineFlags.Remove} to the {@link org.chromium.base.CommandLine}. Note that + * trying to remove a flag set externally, i.e. by the command-line flags file, will not work. + */ + public static void setUp(Context targetContext, AnnotatedElement element) { + Assert.assertNotNull("Unable to get a non-null target context.", targetContext); + CommandLine.reset(); + BaseChromiumApplication.initCommandLine(targetContext); + Set<String> flags = getFlags(element); + for (String flag : flags) { + CommandLine.getInstance().appendSwitch(flag); + } + } + + private static Set<String> getFlags(AnnotatedElement element) { + AnnotatedElement parent = (element instanceof Method) + ? ((Method) element).getDeclaringClass() + : ((Class) element).getSuperclass(); + Set<String> flags = (parent == null) ? new HashSet<String>() : getFlags(parent); + + if (element.isAnnotationPresent(CommandLineFlags.Add.class)) { + flags.addAll(Arrays.asList(element.getAnnotation(CommandLineFlags.Add.class).value())); + } + + if (element.isAnnotationPresent(CommandLineFlags.Remove.class)) { + List<String> flagsToRemove = + Arrays.asList(element.getAnnotation(CommandLineFlags.Remove.class).value()); + for (String flagToRemove : flagsToRemove) { + // If your test fails here, you have tried to remove a command-line flag via + // CommandLineFlags.Remove that was loaded into CommandLine via something other + // than CommandLineFlags.Add (probably the command-line flag file). + Assert.assertFalse("Unable to remove command-line flag \"" + flagToRemove + "\".", + CommandLine.getInstance().hasSwitch(flagToRemove)); + } + flags.removeAll(flagsToRemove); + } + + return flags; + } + private CommandLineFlags() {} }
diff --git a/test/expectations/parser.cc b/test/expectations/parser.cc index c7132e5..83c64c6 100644 --- a/test/expectations/parser.cc +++ b/test/expectations/parser.cc
@@ -39,7 +39,7 @@ Parser::StateFunc Parser::Start() { // If at the start of a line is whitespace, skip it and arrange to come back // here. - if (IsAsciiWhitespace(*pos_)) + if (base::IsAsciiWhitespace(*pos_)) return SkipWhitespaceAndNewLines(&Parser::Start); // Handle comments at the start of lines. @@ -161,7 +161,7 @@ Parser::StateFunc Parser::ExtractString(StateFunc success) { const char* start = pos_; - while (!IsAsciiWhitespace(*pos_) && *pos_ != ']' && HasNext()) { + while (!base::IsAsciiWhitespace(*pos_) && *pos_ != ']' && HasNext()) { ++pos_; if (*pos_ == '#') { return SyntaxError("Unexpected start of comment"); @@ -179,7 +179,7 @@ } Parser::StateFunc Parser::SkipWhitespaceAndNewLines(Parser::StateFunc next) { - while (IsAsciiWhitespace(*pos_) && HasNext()) { + while (base::IsAsciiWhitespace(*pos_) && HasNext()) { if (*pos_ == '\n') { ++line_number_; }
diff --git a/test/histogram_tester.cc b/test/histogram_tester.cc index ea738b0..0eba3a9 100644 --- a/test/histogram_tester.cc +++ b/test/histogram_tester.cc
@@ -73,6 +73,19 @@ } } +std::vector<Bucket> HistogramTester::GetAllSamples(const std::string& name) { + std::vector<Bucket> samples; + scoped_ptr<HistogramSamples> snapshot = + GetHistogramSamplesSinceCreation(name); + for (auto it = snapshot->Iterator(); !it->Done(); it->Next()) { + HistogramBase::Sample sample; + HistogramBase::Count count; + it->Get(&sample, nullptr, &count); + samples.push_back(Bucket(sample, count)); + } + return samples; +} + scoped_ptr<HistogramSamples> HistogramTester::GetHistogramSamplesSinceCreation( const std::string& histogram_name) { HistogramBase* histogram = StatisticsRecorder::FindHistogram(histogram_name); @@ -120,4 +133,12 @@ << expected_count << "). It has (" << actual_count << ")."; } +bool Bucket::operator==(const Bucket& other) const { + return min == other.min && count == other.count; +} + +void PrintTo(const Bucket& bucket, std::ostream* os) { + *os << "Bucket " << bucket.min << ": " << bucket.count; +} + } // namespace base
diff --git a/test/histogram_tester.h b/test/histogram_tester.h index 96317f9..7ac7ca6 100644 --- a/test/histogram_tester.h +++ b/test/histogram_tester.h
@@ -6,7 +6,10 @@ #define BASE_TEST_HISTOGRAM_TESTER_H_ #include <map> +#include <ostream> #include <string> +#include <utility> +#include <vector> #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" @@ -15,6 +18,7 @@ namespace base { +struct Bucket; class HistogramSamples; // HistogramTester provides a simple interface for examining histograms, UMA @@ -47,6 +51,24 @@ void ExpectTotalCount(const std::string& name, base::HistogramBase::Count count) const; + // Returns a list of all of the buckets recorded since creation of this + // object, as vector<Bucket>, where the Bucket represents the min boundary of + // the bucket and the count of samples recorded to that bucket since creation. + // + // Example usage, using gMock: + // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"), + // ElementsAre(Bucket(1, 5), Bucket(2, 10), Bucket(3, 5))); + // + // If you build the expected list programmatically, you can use ContainerEq: + // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"), + // ContainerEq(expected_buckets)); + // + // or EXPECT_EQ if you prefer not to depend on gMock, at the expense of a + // slightly less helpful failure message: + // EXPECT_EQ(expected_buckets, + // histogram_tester.GetAllSamples("HistogramName")); + std::vector<Bucket> GetAllSamples(const std::string& name); + // Access a modified HistogramSamples containing only what has been logged // to the histogram since the creation of this object. scoped_ptr<HistogramSamples> GetHistogramSamplesSinceCreation( @@ -76,6 +98,18 @@ DISALLOW_COPY_AND_ASSIGN(HistogramTester); }; +struct Bucket { + Bucket(base::HistogramBase::Sample min, base::HistogramBase::Count count) + : min(min), count(count) {} + + bool operator==(const Bucket& other) const; + + base::HistogramBase::Sample min; + base::HistogramBase::Count count; +}; + +void PrintTo(const Bucket& value, std::ostream* os); + } // namespace base #endif // BASE_TEST_HISTOGRAM_TESTER_H_
diff --git a/test/histogram_tester_unittest.cc b/test/histogram_tester_unittest.cc index a03ee13..f875527 100644 --- a/test/histogram_tester_unittest.cc +++ b/test/histogram_tester_unittest.cc
@@ -5,16 +5,20 @@ #include "base/test/histogram_tester.h" #include "base/memory/scoped_ptr.h" -#include "base/metrics/histogram.h" +#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_samples.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { +using ::testing::ElementsAre; + const std::string kHistogram1 = "Test1"; const std::string kHistogram2 = "Test2"; const std::string kHistogram3 = "Test3"; const std::string kHistogram4 = "Test4"; +const std::string kHistogram5 = "Test5"; typedef testing::Test HistogramTesterTest; @@ -78,4 +82,15 @@ tester.ExpectTotalCount(kHistogram4, 1); } +TEST_F(HistogramTesterTest, TestGetAllSamples) { + HistogramTester tester; + UMA_HISTOGRAM_ENUMERATION(kHistogram5, 2, 5); + UMA_HISTOGRAM_ENUMERATION(kHistogram5, 3, 5); + UMA_HISTOGRAM_ENUMERATION(kHistogram5, 3, 5); + UMA_HISTOGRAM_ENUMERATION(kHistogram5, 5, 5); + + EXPECT_THAT(tester.GetAllSamples(kHistogram5), + ElementsAre(Bucket(2, 1), Bucket(3, 2), Bucket(5, 1))); +} + } // namespace base
diff --git a/test/launcher/test_launcher.cc b/test/launcher/test_launcher.cc index 7f258f5..7c0bc4e 100644 --- a/test/launcher/test_launcher.cc +++ b/test/launcher/test_launcher.cc
@@ -25,6 +25,7 @@ #include "base/process/kill.h" #include "base/process/launch.h" #include "base/single_thread_task_runner.h" +#include "base/strings/pattern.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" @@ -569,7 +570,7 @@ snippet_lines.begin() + truncated_size); snippet_lines.insert(snippet_lines.begin(), "<truncated>"); } - fprintf(stdout, "%s", JoinString(snippet_lines, "\n").c_str()); + fprintf(stdout, "%s", base::JoinString(snippet_lines, "\n").c_str()); fflush(stdout); }
diff --git a/test/launcher/test_launcher_ios.cc b/test/launcher/test_launcher_ios.cc index ecd31ae..3179bb3 100644 --- a/test/launcher/test_launcher_ios.cc +++ b/test/launcher/test_launcher_ios.cc
@@ -121,7 +121,7 @@ base::CommandLine cmd_line(dir_exe_.AppendASCII(test_name_ + ".app")); cmd_line.AppendSwitchPath(switches::kTestLauncherOutput, output_file); cmd_line.AppendSwitchASCII(base::kGTestFilterFlag, - JoinString(test_names, ":")); + base::JoinString(test_names, ":")); return cmd_line; }
diff --git a/test/launcher/test_launcher_nacl_nonsfi.cc b/test/launcher/test_launcher_nacl_nonsfi.cc index fa52604..173e552 100644 --- a/test/launcher/test_launcher_nacl_nonsfi.cc +++ b/test/launcher/test_launcher_nacl_nonsfi.cc
@@ -112,8 +112,8 @@ base::CommandLine cmd_line(test_path_); cmd_line.AppendSwitchPath( switches::kTestLauncherOutput, output_file); - cmd_line.AppendSwitchASCII( - base::kGTestFilterFlag, JoinString(test_names, ":")); + cmd_line.AppendSwitchASCII(base::kGTestFilterFlag, + base::JoinString(test_names, ":")); return cmd_line; }
diff --git a/test/test_pending_task_unittest.cc b/test/test_pending_task_unittest.cc index 32502f2..7623ce4 100644 --- a/test/test_pending_task_unittest.cc +++ b/test/test_pending_task_unittest.cc
@@ -10,7 +10,7 @@ #include "testing/gtest/include/gtest/gtest-spi.h" #include "testing/gtest/include/gtest/gtest.h" -namespace { +namespace base { TEST(TestPendingTaskTest, TraceSupport) { base::TestPendingTask task; @@ -52,4 +52,4 @@ << task_first << ".ShouldRunBefore(" << task_after << ")\n"; } -} // namespace +} // namespace base
diff --git a/test/test_reg_util_win.cc b/test/test_reg_util_win.cc index e3b1ffc..336cc45 100644 --- a/test/test_reg_util_win.cc +++ b/test/test_reg_util_win.cc
@@ -7,6 +7,7 @@ #include "base/guid.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" @@ -32,8 +33,10 @@ test_key_root.c_str()); for (; iterator_test_root_key.Valid(); ++iterator_test_root_key) { base::string16 key_name = iterator_test_root_key.Name(); - std::vector<base::string16> tokens; - if (!Tokenize(key_name, base::string16(kTimestampDelimiter), &tokens)) + std::vector<base::string16> tokens = + base::SplitString(key_name, kTimestampDelimiter, base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + if (tokens.empty()) continue; int64 key_name_as_number = 0;
diff --git a/test/test_suite.cc b/test/test_suite.cc index b6fb2ef..aef6906 100644 --- a/test/test_suite.cc +++ b/test/test_suite.cc
@@ -332,7 +332,7 @@ i18n::SetICUDefaultLocale("en_US"); #else std::string default_locale(uloc_getDefault()); - if (EndsWith(default_locale, "POSIX", false)) + if (EndsWith(default_locale, "POSIX", CompareCase::INSENSITIVE_ASCII)) i18n::SetICUDefaultLocale("en_US"); #endif #endif
diff --git a/test/test_support_ios.mm b/test/test_support_ios.mm index 3b31da6..9e82612 100644 --- a/test/test_support_ios.mm +++ b/test/test_support_ios.mm
@@ -80,6 +80,10 @@ label.textAlignment = NSTextAlignmentCenter; [window_ addSubview:label]; + // An NSInternalInconsistencyException is thrown if the app doesn't have a + // root view controller. Set an empty one here. + [window_ setRootViewController:[[[UIViewController alloc] init] autorelease]]; + if ([self shouldRedirectOutputToFile]) [self redirectOutput];
diff --git a/test/trace_event_analyzer.cc b/test/trace_event_analyzer.cc index e46c2a5..93d7f30 100644 --- a/test/trace_event_analyzer.cc +++ b/test/trace_event_analyzer.cc
@@ -10,6 +10,7 @@ #include "base/json/json_reader.h" #include "base/memory/scoped_ptr.h" +#include "base/strings/pattern.h" #include "base/values.h" namespace trace_analyzer { @@ -321,17 +322,17 @@ switch (operator_) { case OP_EQ: if (right().is_pattern_) - *result = MatchPattern(lhs, rhs); + *result = base::MatchPattern(lhs, rhs); else if (left().is_pattern_) - *result = MatchPattern(rhs, lhs); + *result = base::MatchPattern(rhs, lhs); else *result = (lhs == rhs); return true; case OP_NE: if (right().is_pattern_) - *result = !MatchPattern(lhs, rhs); + *result = !base::MatchPattern(lhs, rhs); else if (left().is_pattern_) - *result = !MatchPattern(rhs, lhs); + *result = !base::MatchPattern(rhs, lhs); else *result = (lhs != rhs); return true;
diff --git a/third_party/nspr/prtime.cc b/third_party/nspr/prtime.cc index 9335b01..a7c5a3a 100644 --- a/third_party/nspr/prtime.cc +++ b/third_party/nspr/prtime.cc
@@ -107,9 +107,9 @@ static const PRTime kSecondsToMicroseconds = static_cast<PRTime>(1000000); #if defined(OS_WIN) // Create the system struct representing our exploded time. - SYSTEMTIME st = {0}; - FILETIME ft = {0}; - ULARGE_INTEGER uli = {0}; + SYSTEMTIME st = {}; + FILETIME ft = {}; + ULARGE_INTEGER uli = {}; st.wYear = exploded->tm_year; st.wMonth = static_cast<WORD>(exploded->tm_month + 1);
diff --git a/third_party/symbolize/symbolize.cc b/third_party/symbolize/symbolize.cc index b25f747..f4861df 100644 --- a/third_party/symbolize/symbolize.cc +++ b/third_party/symbolize/symbolize.cc
@@ -651,7 +651,8 @@ // Handle negative numbers (only for base 10). if (i < 0 && base == 10) { - j = -i; + // This does "j = -i" while avoiding integer overflow. + j = static_cast<uintptr_t>(-(i + 1)) + 1; // Make sure we can write the '-' character. if (++n > sz) {
diff --git a/threading/platform_thread.h b/threading/platform_thread.h index 3468f45..0b92265 100644 --- a/threading/platform_thread.h +++ b/threading/platform_thread.h
@@ -89,6 +89,7 @@ id_(id) { } + // TODO(toyoshim): Remove id() and use PlatformThread::CurrentId() instead. PlatformThreadId id() const { return id_; } @@ -112,8 +113,8 @@ const PlatformThreadId kInvalidThreadId(0); -// Valid values for SetThreadPriority(), listed in increasing order of -// importance. +// Valid values for priority of Thread::Options and SimpleThread::Options, and +// SetCurrentThreadPriority(), listed in increasing order of importance. enum class ThreadPriority { // Suitable for threads that shouldn't disrupt high priority work. BACKGROUND, @@ -176,8 +177,7 @@ PlatformThreadHandle* thread_handle); // CreateWithPriority() does the same thing as Create() except the priority of - // the thread is set based on |priority|. Can be used in place of Create() - // followed by SetThreadPriority(). + // the thread is set based on |priority|. static bool CreateWithPriority(size_t stack_size, Delegate* delegate, PlatformThreadHandle* thread_handle, ThreadPriority priority); @@ -192,15 +192,15 @@ // |thread_handle|. static void Join(PlatformThreadHandle thread_handle); - // Toggles the target thread's priority at runtime. Prefer - // CreateWithPriority() to set the thread's initial priority. - // NOTE: The call may fail if the caller thread is not the same as the - // target thread on POSIX. For example, seccomp-bpf blocks it by default - // in the sandbox. - static void SetThreadPriority(PlatformThreadHandle handle, - ThreadPriority priority); + // Toggles the current thread's priority at runtime. A thread may not be able + // to raise its priority back up after lowering it if the process does not + // have a proper permission, e.g. CAP_SYS_NICE on Linux. + // Since changing other threads' priority is not permitted in favor of + // security, this interface is restricted to change only the current thread + // priority (https://crbug.com/399473). + static void SetCurrentThreadPriority(ThreadPriority priority); - static ThreadPriority GetThreadPriority(PlatformThreadHandle handle); + static ThreadPriority GetCurrentThreadPriority(); private: DISALLOW_IMPLICIT_CONSTRUCTORS(PlatformThread);
diff --git a/threading/platform_thread_android.cc b/threading/platform_thread_android.cc index 11e5e2e..176a6bd 100644 --- a/threading/platform_thread_android.cc +++ b/threading/platform_thread_android.cc
@@ -23,29 +23,21 @@ namespace internal { -// These nice values are taken from Android, which uses nice values like linux, -// but defines some preset nice values. -// Process.THREAD_PRIORITY_AUDIO = -16 -// Process.THREAD_PRIORITY_BACKGROUND = 10 -// Process.THREAD_PRIORITY_DEFAULT = 0; -// Process.THREAD_PRIORITY_DISPLAY = -4; -// Process.THREAD_PRIORITY_FOREGROUND = -2; -// Process.THREAD_PRIORITY_LESS_FAVORABLE = 1; -// Process.THREAD_PRIORITY_LOWEST = 19; -// Process.THREAD_PRIORITY_MORE_FAVORABLE = -1; -// Process.THREAD_PRIORITY_URGENT_AUDIO = -19; -// Process.THREAD_PRIORITY_URGENT_DISPLAY = -8; -// We use -6 for display, but we may want to split this into urgent (-8) and -// non-urgent (-4). +// - BACKGROUND is 9 due to it being the nicest value we can use that's still +// above an Android system threshold that enables heavy throttling starting at +// 10; we want to be lower-priority than Chrome's other threads without +// incurring this behavior. +// - DISPLAY is -6 due to being midway between Android's DISPLAY (-4) and +// URGENT_DISPLAY (-8). +// - REALTIME_AUDIO corresponds to Android's THREAD_PRIORITY_AUDIO = -16 value. const ThreadPriorityToNiceValuePair kThreadPriorityToNiceValueMap[4] = { - {ThreadPriority::BACKGROUND, 10}, + {ThreadPriority::BACKGROUND, 9}, {ThreadPriority::NORMAL, 0}, {ThreadPriority::DISPLAY, -6}, {ThreadPriority::REALTIME_AUDIO, -16}, }; -bool SetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority priority) { +bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority) { // On Android, we set the Audio priority through JNI as Audio priority // will also allow the process to run while it is backgrounded. if (priority == ThreadPriority::REALTIME_AUDIO) { @@ -56,8 +48,8 @@ return false; } -bool GetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority* priority) { +bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority) { + // See http://crbug.com/505474. NOTIMPLEMENTED(); return false; } @@ -88,8 +80,7 @@ void InitOnThread() { // Threads on linux/android may inherit their priority from the thread // where they were created. This sets all new threads to the default. - PlatformThread::SetThreadPriority(PlatformThread::CurrentHandle(), - ThreadPriority::NORMAL); + PlatformThread::SetCurrentThreadPriority(ThreadPriority::NORMAL); } void TerminateOnThread() {
diff --git a/threading/platform_thread_freebsd.cc b/threading/platform_thread_freebsd.cc index f4fded0..e29e865 100644 --- a/threading/platform_thread_freebsd.cc +++ b/threading/platform_thread_freebsd.cc
@@ -36,11 +36,8 @@ {ThreadPriority::REALTIME_AUDIO, -10}, } -bool SetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority priority) { +bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority) { #if !defined(OS_NACL) - // TODO(gab): Assess the correctness of using |pthread_self()| below instead - // of |handle|. http://crbug.com/468793. return priority == ThreadPriority::REALTIME_AUDIO && pthread_setschedparam(pthread_self(), SCHED_RR, &kRealTimePrio) == 0; #else @@ -48,11 +45,8 @@ #endif } -bool GetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority* priority) { +bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority) { #if !defined(OS_NACL) - // TODO(gab): Assess the correctness of using |pthread_self()| below instead - // of |handle|. http://crbug.com/468793. int maybe_sched_rr = 0; struct sched_param maybe_realtime_prio = {0}; if (pthread_getschedparam(pthread_self(), &maybe_sched_rr,
diff --git a/threading/platform_thread_internal_posix.h b/threading/platform_thread_internal_posix.h index 62006ce..05a8d1e 100644 --- a/threading/platform_thread_internal_posix.h +++ b/threading/platform_thread_internal_posix.h
@@ -26,17 +26,15 @@ ThreadPriority NiceValueToThreadPriority(int nice_value); // Allows platform specific tweaks to the generic POSIX solution for -// SetThreadPriority. Returns true if the platform-specific implementation -// handled this |priority| change, false if the generic implementation should -// instead proceed. -bool SetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority priority); +// SetCurrentThreadPriority. Returns true if the platform-specific +// implementation handled this |priority| change, false if the generic +// implementation should instead proceed. +bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority); -// Returns true if there is a platform-specific ThreadPriority set on |handle| -// (and returns the actual ThreadPriority via |priority|). Returns false -// otherwise, leaving |priority| untouched. -bool GetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority* priority); +// Returns true if there is a platform-specific ThreadPriority set on the +// current thread (and returns the actual ThreadPriority via |priority|). +// Returns false otherwise, leaving |priority| untouched. +bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority); } // namespace internal
diff --git a/threading/platform_thread_linux.cc b/threading/platform_thread_linux.cc index 9f74374..48cf744 100644 --- a/threading/platform_thread_linux.cc +++ b/threading/platform_thread_linux.cc
@@ -27,6 +27,7 @@ namespace { #if !defined(OS_NACL) const struct sched_param kRealTimePrio = {8}; +const struct sched_param kResetPrio = {0}; #endif } // namespace @@ -37,11 +38,18 @@ {ThreadPriority::REALTIME_AUDIO, -10}, }; -bool SetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority priority) { +bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority) { #if !defined(OS_NACL) - // TODO(gab): Assess the correctness of using |pthread_self()| below instead - // of |handle|. http://crbug.com/468793. + ThreadPriority current_priority; + if (priority != ThreadPriority::REALTIME_AUDIO && + GetCurrentThreadPriorityForPlatform(¤t_priority) && + current_priority == ThreadPriority::REALTIME_AUDIO) { + // If the pthread's round-robin scheduler is already enabled, and the new + // priority will use setpriority() instead, the pthread scheduler should be + // reset to use SCHED_OTHER so that setpriority() just works. + pthread_setschedparam(pthread_self(), SCHED_OTHER, &kResetPrio); + return false; + } return priority == ThreadPriority::REALTIME_AUDIO && pthread_setschedparam(pthread_self(), SCHED_RR, &kRealTimePrio) == 0; #else @@ -49,13 +57,10 @@ #endif } -bool GetThreadPriorityForPlatform(PlatformThreadHandle handle, - ThreadPriority* priority) { +bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority) { #if !defined(OS_NACL) int maybe_sched_rr = 0; struct sched_param maybe_realtime_prio = {0}; - // TODO(gab): Assess the correctness of using |pthread_self()| below instead - // of |handle|. http://crbug.com/468793. if (pthread_getschedparam(pthread_self(), &maybe_sched_rr, &maybe_realtime_prio) == 0 && maybe_sched_rr == SCHED_RR &&
diff --git a/threading/platform_thread_mac.mm b/threading/platform_thread_mac.mm index 813cae2..1ecbcd6 100644 --- a/threading/platform_thread_mac.mm +++ b/threading/platform_thread_mac.mm
@@ -155,10 +155,10 @@ } // anonymous namespace // static -void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, - ThreadPriority priority) { +void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) { // Convert from pthread_t to mach thread identifier. - mach_port_t mach_thread_id = pthread_mach_thread_np(handle.platform_handle()); + mach_port_t mach_thread_id = + pthread_mach_thread_np(PlatformThread::CurrentHandle().platform_handle()); switch (priority) { case ThreadPriority::NORMAL: @@ -174,7 +174,7 @@ } // static -ThreadPriority PlatformThread::GetThreadPriority(PlatformThreadHandle handle) { +ThreadPriority PlatformThread::GetCurrentThreadPriority() { NOTIMPLEMENTED(); return ThreadPriority::NORMAL; }
diff --git a/threading/platform_thread_posix.cc b/threading/platform_thread_posix.cc index 0d821a9..f3a835e 100644 --- a/threading/platform_thread_posix.cc +++ b/threading/platform_thread_posix.cc
@@ -58,10 +58,8 @@ if (!thread_params->joinable) base::ThreadRestrictions::SetSingletonAllowed(false); - if (thread_params->priority != ThreadPriority::NORMAL) { - PlatformThread::SetThreadPriority(PlatformThread::CurrentHandle(), - thread_params->priority); - } + if (thread_params->priority != ThreadPriority::NORMAL) + PlatformThread::SetCurrentThreadPriority(thread_params->priority); // Stash the id in the handle so the calling thread has a complete // handle, and unblock the parent thread. @@ -231,16 +229,15 @@ CHECK_EQ(0, pthread_join(thread_handle.platform_handle(), NULL)); } -// Mac has its own Set/GetThreadPriority() implementations. +// Mac has its own Set/GetCurrentThreadPriority() implementations. #if !defined(OS_MACOSX) // static -void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, - ThreadPriority priority) { +void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) { #if defined(OS_NACL) NOTIMPLEMENTED(); #else - if (internal::SetThreadPriorityForPlatform(handle, priority)) + if (internal::SetCurrentThreadPriorityForPlatform(priority)) return; // setpriority(2) should change the whole thread group's (i.e. process) @@ -249,39 +246,34 @@ // Linux/NPTL implementation of POSIX threads, the nice value is a per-thread // attribute". Also, 0 is prefered to the current thread id since it is // equivalent but makes sandboxing easier (https://crbug.com/399473). - DCHECK_NE(handle.id(), kInvalidThreadId); const int nice_setting = internal::ThreadPriorityToNiceValue(priority); - const PlatformThreadId current_id = PlatformThread::CurrentId(); - if (setpriority(PRIO_PROCESS, handle.id() == current_id ? 0 : handle.id(), - nice_setting)) { - DVPLOG(1) << "Failed to set nice value of thread (" << handle.id() - << ") to " << nice_setting; + if (setpriority(PRIO_PROCESS, 0, nice_setting)) { + DVPLOG(1) << "Failed to set nice value of thread (" + << PlatformThread::CurrentId() << ") to " << nice_setting; } #endif // defined(OS_NACL) } // static -ThreadPriority PlatformThread::GetThreadPriority(PlatformThreadHandle handle) { +ThreadPriority PlatformThread::GetCurrentThreadPriority() { #if defined(OS_NACL) NOTIMPLEMENTED(); return ThreadPriority::NORMAL; #else - // Mirrors SetThreadPriority()'s implementation. + // Mirrors SetCurrentThreadPriority()'s implementation. ThreadPriority platform_specific_priority; - if (internal::GetThreadPriorityForPlatform(handle, - &platform_specific_priority)) { + if (internal::GetCurrentThreadPriorityForPlatform( + &platform_specific_priority)) { return platform_specific_priority; } - DCHECK_NE(handle.id(), kInvalidThreadId); - const PlatformThreadId current_id = PlatformThread::CurrentId(); // Need to clear errno before calling getpriority(): // http://man7.org/linux/man-pages/man2/getpriority.2.html errno = 0; - int nice_value = - getpriority(PRIO_PROCESS, handle.id() == current_id ? 0 : handle.id()); + int nice_value = getpriority(PRIO_PROCESS, 0); if (errno != 0) { - DVPLOG(1) << "Failed to get nice value of thread (" << handle.id() << ")"; + DVPLOG(1) << "Failed to get nice value of thread (" + << PlatformThread::CurrentId() << ")"; return ThreadPriority::NORMAL; }
diff --git a/threading/platform_thread_unittest.cc b/threading/platform_thread_unittest.cc index c4b3d5d..0fd9bc4 100644 --- a/threading/platform_thread_unittest.cc +++ b/threading/platform_thread_unittest.cc
@@ -8,7 +8,10 @@ #include "base/threading/platform_thread.h" #include "testing/gtest/include/gtest/gtest.h" -#if defined(OS_WIN) +#if defined(OS_POSIX) +#include <sys/types.h> +#include <unistd.h> +#elif defined(OS_WIN) #include <windows.h> #endif @@ -16,6 +19,8 @@ // Trivial tests that thread runs and doesn't crash on create and join --------- +namespace { + class TrivialThread : public PlatformThread::Delegate { public: TrivialThread() : did_run_(false) {} @@ -30,6 +35,8 @@ DISALLOW_COPY_AND_ASSIGN(TrivialThread); }; +} // namespace + TEST(PlatformThreadTest, Trivial) { TrivialThread thread; PlatformThreadHandle handle; @@ -56,11 +63,13 @@ // Tests of basic thread functions --------------------------------------------- +namespace { + class FunctionTestThread : public PlatformThread::Delegate { public: FunctionTestThread() : thread_id_(kInvalidThreadId), - thread_started_(true, false), + termination_ready_(true, false), terminate_thread_(true, false), done_(false) {} ~FunctionTestThread() override { @@ -70,8 +79,9 @@ << "WaitableEvent blocking the underlying thread's main."; } - // Grabs |thread_id_|, signals |thread_started_|, and then waits for - // |terminate_thread_| to be signaled before exiting. + // Grabs |thread_id_|, runs an optional test on that thread, signals + // |termination_ready_|, and then waits for |terminate_thread_| to be + // signaled before exiting. void ThreadMain() override { thread_id_ = PlatformThread::CurrentId(); EXPECT_NE(thread_id_, kInvalidThreadId); @@ -79,39 +89,44 @@ // Make sure that the thread ID is the same across calls. EXPECT_EQ(thread_id_, PlatformThread::CurrentId()); - thread_started_.Signal(); + // Run extra tests. + RunTest(); + termination_ready_.Signal(); terminate_thread_.Wait(); done_ = true; } PlatformThreadId thread_id() const { - EXPECT_TRUE(thread_started_.IsSignaled()) << "Thread ID still unknown"; + EXPECT_TRUE(termination_ready_.IsSignaled()) << "Thread ID still unknown"; return thread_id_; } - bool IsRunning() const { - return thread_started_.IsSignaled() && !done_; - } + bool IsRunning() const { return termination_ready_.IsSignaled() && !done_; } - // Blocks until this thread is started. - void WaitForThreadStart() { thread_started_.Wait(); } + // Blocks until this thread is started and ready to be terminated. + void WaitForTerminationReady() { termination_ready_.Wait(); } - // Mark this thread for termination (callers must then join this thread to be + // Marks this thread for termination (callers must then join this thread to be // guaranteed of termination). void MarkForTermination() { terminate_thread_.Signal(); } private: + // Runs an optional test on the newly created thread. + virtual void RunTest() {} + PlatformThreadId thread_id_; - mutable WaitableEvent thread_started_; + mutable WaitableEvent termination_ready_; WaitableEvent terminate_thread_; bool done_; DISALLOW_COPY_AND_ASSIGN(FunctionTestThread); }; +} // namespace + TEST(PlatformThreadTest, Function) { PlatformThreadId main_thread_id = PlatformThread::CurrentId(); @@ -120,7 +135,7 @@ ASSERT_FALSE(thread.IsRunning()); ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); - thread.WaitForThreadStart(); + thread.WaitForTerminationReady(); ASSERT_TRUE(thread.IsRunning()); EXPECT_NE(thread.thread_id(), main_thread_id); @@ -144,7 +159,7 @@ for (size_t n = 0; n < arraysize(thread); n++) ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); for (size_t n = 0; n < arraysize(thread); n++) - thread[n].WaitForThreadStart(); + thread[n].WaitForTerminationReady(); for (size_t n = 0; n < arraysize(thread); n++) { ASSERT_TRUE(thread[n].IsRunning()); @@ -170,106 +185,85 @@ namespace { const ThreadPriority kThreadPriorityTestValues[] = { -// Disable non-normal priority toggling on POSIX as it appears to be broken -// (http://crbug.com/468793). This is prefered to disabling the tests altogether -// on POSIX as it at least provides coverage for running this code under -// "normal" priority. -#if !defined(OS_POSIX) - ThreadPriority::DISPLAY, +// The order should be higher to lower to cover as much cases as possible on +// Linux trybots running without CAP_SYS_NICE permission. +#if !defined(OS_ANDROID) + // PlatformThread::GetCurrentThreadPriority() on Android does not support + // REALTIME_AUDIO case. See http://crbug.com/505474. ThreadPriority::REALTIME_AUDIO, - // Keep BACKGROUND second to last to test backgrounding from other - // priorities. - ThreadPriority::BACKGROUND, -#endif // !defined(OS_POSIX) - // Keep NORMAL last to test unbackgrounding. - ThreadPriority::NORMAL +#endif + ThreadPriority::DISPLAY, + // This redundant BACKGROUND priority is to test backgrounding from other + // priorities, and unbackgrounding. + ThreadPriority::BACKGROUND, ThreadPriority::NORMAL, + ThreadPriority::BACKGROUND}; + +bool IsBumpingPriorityAllowed() { +#if defined(OS_POSIX) + // Only root can raise thread priority on POSIX environment. On Linux, users + // who have CAP_SYS_NICE permission also can raise the thread priority, but + // libcap.so would be needed to check the capability. + return geteuid() == 0; +#else + return true; +#endif +} + +class ThreadPriorityTestThread : public FunctionTestThread { + public: + ThreadPriorityTestThread() = default; + ~ThreadPriorityTestThread() override = default; + + private: + void RunTest() override { + // Confirm that the current thread's priority is as expected. + EXPECT_EQ(ThreadPriority::NORMAL, + PlatformThread::GetCurrentThreadPriority()); + + // Toggle each supported priority on the current thread and confirm it + // affects it. + const bool bumping_priority_allowed = IsBumpingPriorityAllowed(); + for (size_t i = 0; i < arraysize(kThreadPriorityTestValues); ++i) { + SCOPED_TRACE(i); + if (!bumping_priority_allowed && + kThreadPriorityTestValues[i] > + PlatformThread::GetCurrentThreadPriority()) { + continue; + } + + // Alter and verify the current thread's priority. + PlatformThread::SetCurrentThreadPriority(kThreadPriorityTestValues[i]); + EXPECT_EQ(kThreadPriorityTestValues[i], + PlatformThread::GetCurrentThreadPriority()); + } + } + + DISALLOW_COPY_AND_ASSIGN(ThreadPriorityTestThread); }; } // namespace -// Test changing another thread's priority. -// NOTE: This test is partially disabled on POSIX, see note above and -// http://crbug.com/468793. -TEST(PlatformThreadTest, ThreadPriorityOtherThread) { - PlatformThreadHandle current_handle(PlatformThread::CurrentHandle()); +#if defined(OS_MACOSX) +// PlatformThread::GetCurrentThreadPriority() is not implemented on OS X. +#define MAYBE_ThreadPriorityCurrentThread DISABLED_ThreadPriorityCurrentThread +#else +#define MAYBE_ThreadPriorityCurrentThread ThreadPriorityCurrentThread +#endif - // Confirm that the current thread's priority is as expected. - EXPECT_EQ(ThreadPriority::NORMAL, - PlatformThread::GetThreadPriority(current_handle)); - - // Create a test thread. - FunctionTestThread thread; - PlatformThreadHandle handle; - ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); - thread.WaitForThreadStart(); - EXPECT_NE(thread.thread_id(), kInvalidThreadId); - EXPECT_NE(thread.thread_id(), PlatformThread::CurrentId()); - - // New threads should get normal priority by default. - EXPECT_EQ(ThreadPriority::NORMAL, PlatformThread::GetThreadPriority(handle)); - - // Toggle each supported priority on the test thread and confirm it only - // affects it (and not the current thread). - for (size_t i = 0; i < arraysize(kThreadPriorityTestValues); ++i) { - SCOPED_TRACE(i); - - // Alter and verify the test thread's priority. - PlatformThread::SetThreadPriority(handle, kThreadPriorityTestValues[i]); - EXPECT_EQ(kThreadPriorityTestValues[i], - PlatformThread::GetThreadPriority(handle)); - - // Make sure the current thread was otherwise unaffected. - EXPECT_EQ(ThreadPriority::NORMAL, - PlatformThread::GetThreadPriority(current_handle)); - } - - thread.MarkForTermination(); - PlatformThread::Join(handle); -} - -// Test changing the current thread's priority (which has different semantics on +// Test changing a created thread's priority (which has different semantics on // some platforms). -// NOTE: This test is partially disabled on POSIX, see note above and -// http://crbug.com/468793. -TEST(PlatformThreadTest, ThreadPriorityCurrentThread) { - PlatformThreadHandle current_handle(PlatformThread::CurrentHandle()); - - // Confirm that the current thread's priority is as expected. - EXPECT_EQ(ThreadPriority::NORMAL, - PlatformThread::GetThreadPriority(current_handle)); - - // Create a test thread for verification purposes only. - FunctionTestThread thread; +TEST(PlatformThreadTest, MAYBE_ThreadPriorityCurrentThread) { + ThreadPriorityTestThread thread; PlatformThreadHandle handle; + + ASSERT_FALSE(thread.IsRunning()); ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); - thread.WaitForThreadStart(); - EXPECT_NE(thread.thread_id(), kInvalidThreadId); - EXPECT_NE(thread.thread_id(), PlatformThread::CurrentId()); - - // Confirm that the new thread's priority is as expected. - EXPECT_EQ(ThreadPriority::NORMAL, PlatformThread::GetThreadPriority(handle)); - - // Toggle each supported priority on the current thread and confirm it only - // affects it (and not the test thread). - for (size_t i = 0; i < arraysize(kThreadPriorityTestValues); ++i) { - SCOPED_TRACE(i); - - // Alter and verify the current thread's priority. - PlatformThread::SetThreadPriority(current_handle, - kThreadPriorityTestValues[i]); - EXPECT_EQ(kThreadPriorityTestValues[i], - PlatformThread::GetThreadPriority(current_handle)); - - // Make sure the test thread was otherwise unaffected. - EXPECT_EQ(ThreadPriority::NORMAL, - PlatformThread::GetThreadPriority(handle)); - } - - // Restore current thread priority for follow-up tests. - PlatformThread::SetThreadPriority(current_handle, ThreadPriority::NORMAL); + thread.WaitForTerminationReady(); + ASSERT_TRUE(thread.IsRunning()); thread.MarkForTermination(); PlatformThread::Join(handle); + ASSERT_FALSE(thread.IsRunning()); } } // namespace base
diff --git a/threading/platform_thread_win.cc b/threading/platform_thread_win.cc index 395fc9e..be3f410 100644 --- a/threading/platform_thread_win.cc +++ b/threading/platform_thread_win.cc
@@ -46,6 +46,7 @@ struct ThreadParams { PlatformThread::Delegate* delegate; bool joinable; + ThreadPriority priority; }; DWORD __stdcall ThreadFunc(void* params) { @@ -54,6 +55,9 @@ if (!thread_params->joinable) base::ThreadRestrictions::SetSingletonAllowed(false); + if (thread_params->priority != ThreadPriority::NORMAL) + PlatformThread::SetCurrentThreadPriority(thread_params->priority); + // Retrieve a copy of the thread handle to use as the key in the // thread name mapping. PlatformThreadHandle::Handle platform_handle; @@ -86,12 +90,13 @@ return NULL; } -// CreateThreadInternal() matches PlatformThread::Create(), except that -// |out_thread_handle| may be NULL, in which case a non-joinable thread is +// CreateThreadInternal() matches PlatformThread::CreateWithPriority(), except +// that |out_thread_handle| may be NULL, in which case a non-joinable thread is // created. bool CreateThreadInternal(size_t stack_size, PlatformThread::Delegate* delegate, - PlatformThreadHandle* out_thread_handle) { + PlatformThreadHandle* out_thread_handle, + ThreadPriority priority) { unsigned int flags = 0; if (stack_size > 0 && base::win::GetVersion() >= base::win::VERSION_XP) { flags = STACK_SIZE_PARAM_IS_A_RESERVATION; @@ -102,6 +107,7 @@ ThreadParams* params = new ThreadParams; params->delegate = delegate; params->joinable = out_thread_handle != NULL; + params->priority = priority; // Using CreateThread here vs _beginthreadex makes thread creation a bit // faster and doesn't require the loader lock to be available. Our code will @@ -185,23 +191,22 @@ // static bool PlatformThread::Create(size_t stack_size, Delegate* delegate, PlatformThreadHandle* thread_handle) { - DCHECK(thread_handle); - return CreateThreadInternal(stack_size, delegate, thread_handle); + return CreateWithPriority(stack_size, delegate, thread_handle, + ThreadPriority::NORMAL); } // static bool PlatformThread::CreateWithPriority(size_t stack_size, Delegate* delegate, PlatformThreadHandle* thread_handle, ThreadPriority priority) { - bool result = Create(stack_size, delegate, thread_handle); - if (result) - SetThreadPriority(*thread_handle, priority); - return result; + DCHECK(thread_handle); + return CreateThreadInternal(stack_size, delegate, thread_handle, priority); } // static bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) { - return CreateThreadInternal(stack_size, delegate, NULL); + return CreateThreadInternal(stack_size, delegate, NULL, + ThreadPriority::NORMAL); } // static @@ -231,10 +236,7 @@ } // static -void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, - ThreadPriority priority) { - DCHECK(!handle.is_null()); - +void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) { int desired_priority = THREAD_PRIORITY_ERROR_RETURN; switch (priority) { case ThreadPriority::BACKGROUND: @@ -258,16 +260,16 @@ #ifndef NDEBUG const BOOL success = #endif - ::SetThreadPriority(handle.platform_handle(), desired_priority); + ::SetThreadPriority(PlatformThread::CurrentHandle().platform_handle(), + desired_priority); DPLOG_IF(ERROR, !success) << "Failed to set thread priority to " << desired_priority; } // static -ThreadPriority PlatformThread::GetThreadPriority(PlatformThreadHandle handle) { - DCHECK(!handle.is_null()); - - int priority = ::GetThreadPriority(handle.platform_handle()); +ThreadPriority PlatformThread::GetCurrentThreadPriority() { + int priority = + ::GetThreadPriority(PlatformThread::CurrentHandle().platform_handle()); switch (priority) { case THREAD_PRIORITY_LOWEST: return ThreadPriority::BACKGROUND;
diff --git a/threading/post_task_and_reply_impl.h b/threading/post_task_and_reply_impl.h index a5b9580..d21ab78 100644 --- a/threading/post_task_and_reply_impl.h +++ b/threading/post_task_and_reply_impl.h
@@ -25,6 +25,8 @@ // may want base::WorkerPool. class PostTaskAndReplyImpl { public: + virtual ~PostTaskAndReplyImpl() = default; + // Implementation for TaskRunner::PostTaskAndReply and // WorkerPool::PostTaskAndReply. bool PostTaskAndReply(const tracked_objects::Location& from_here,
diff --git a/threading/simple_thread.h b/threading/simple_thread.h index 36548d3..2f0eb4d 100644 --- a/threading/simple_thread.h +++ b/threading/simple_thread.h
@@ -59,8 +59,10 @@ public: class BASE_EXPORT Options { public: - Options() : stack_size_(0), priority_(ThreadPriority::NORMAL) { } - ~Options() { } + Options() : stack_size_(0), priority_(ThreadPriority::NORMAL) {} + explicit Options(ThreadPriority priority) + : stack_size_(0), priority_(priority) {} + ~Options() {} // We use the standard compiler-supplied copy constructor. @@ -109,12 +111,6 @@ // Overridden from PlatformThread::Delegate: void ThreadMain() override; - // Only set priorities with a careful understanding of the consequences. - // This is meant for very limited use cases. - void SetThreadPriority(ThreadPriority priority) { - PlatformThread::SetThreadPriority(thread_, priority); - } - private: const std::string name_prefix_; std::string name_;
diff --git a/threading/thread.cc b/threading/thread.cc index 63b07cb..9c4535b 100644 --- a/threading/thread.cc +++ b/threading/thread.cc
@@ -94,8 +94,9 @@ type = MessageLoop::TYPE_CUSTOM; message_loop_timer_slack_ = options.timer_slack; - message_loop_ = new MessageLoop(type, options.message_pump_factory); - + scoped_ptr<MessageLoop> message_loop = + MessageLoop::CreateUnbound(type, options.message_pump_factory); + message_loop_ = message_loop.get(); start_event_.reset(new WaitableEvent(false, false)); // Hold the thread_lock_ while starting a new thread, so that we can make sure @@ -111,13 +112,16 @@ } if (!created) { DLOG(ERROR) << "failed to create thread"; - delete message_loop_; message_loop_ = nullptr; start_event_.reset(); return false; } } + // The ownership of message_loop is managemed by the newly created thread + // within the ThreadMain. + ignore_result(message_loop.release()); + DCHECK(message_loop_); return true; } @@ -190,13 +194,6 @@ return running_; } -void Thread::SetPriority(ThreadPriority priority) { - // The thread must be started (and id known) for this to be - // compatible with all platforms. - DCHECK(message_loop_ != nullptr); - PlatformThread::SetThreadPriority(thread_, priority); -} - void Thread::Run(MessageLoop* message_loop) { message_loop->Run(); }
diff --git a/threading/thread.h b/threading/thread.h index 0bd5d12..5126491 100644 --- a/threading/thread.h +++ b/threading/thread.h
@@ -155,25 +155,13 @@ // MessageLoop* message_loop() const { return message_loop_; } - // Returns a MessageLoopProxy for this thread. Use the MessageLoopProxy's - // PostTask methods to execute code on the thread. Returns NULL if the thread - // is not running (e.g. before Start or after Stop have been called). Callers - // can hold on to this even after the thread is gone; in this situation, - // attempts to PostTask() will fail. - // - // Note: This method is deprecated. Callers should call task_runner() instead - // and use the TaskRunner interfaces for safely interfacing with the Thread. - scoped_refptr<MessageLoopProxy> message_loop_proxy() const { - return message_loop_ ? message_loop_->message_loop_proxy() : NULL; - } - // Returns a TaskRunner for this thread. Use the TaskRunner's PostTask // methods to execute code on the thread. Returns NULL if the thread is not // running (e.g. before Start or after Stop have been called). Callers can // hold on to this even after the thread is gone; in this situation, attempts // to PostTask() will fail. scoped_refptr<SingleThreadTaskRunner> task_runner() const { - return message_loop_->task_runner(); + return message_loop_ ? message_loop_->task_runner() : nullptr; } // Returns the name of this thread (for display in debugger too). @@ -188,9 +176,6 @@ // Returns true if the thread has been started, and not yet stopped. bool IsRunning() const; - // Sets the thread priority. The thread must already be started. - void SetPriority(ThreadPriority priority); - protected: // Called just prior to starting the message loop virtual void Init() {}
diff --git a/threading/thread_restrictions.h b/threading/thread_restrictions.h index 54f50eb..6f3d705 100644 --- a/threading/thread_restrictions.h +++ b/threading/thread_restrictions.h
@@ -58,6 +58,7 @@ } } namespace net { +class NetworkChangeNotifierMac; namespace internal { class AddressTrackerLinux; } @@ -206,6 +207,7 @@ friend class disk_cache::BackendImpl; // http://crbug.com/74623 friend class disk_cache::InFlightIO; // http://crbug.com/74623 friend class net::internal::AddressTrackerLinux; // http://crbug.com/125097 + friend class net::NetworkChangeNotifierMac; // http://crbug.com/125097 friend class ::BrowserProcessImpl; // http://crbug.com/125207 friend class ::NativeBackendKWallet; // http://crbug.com/125331 // END USAGE THAT NEEDS TO BE FIXED.
diff --git a/threading/thread_unittest.cc b/threading/thread_unittest.cc index e86c758..3c35416 100644 --- a/threading/thread_unittest.cc +++ b/threading/thread_unittest.cc
@@ -234,3 +234,8 @@ EXPECT_EQ(THREAD_EVENT_CLEANUP, captured_events[1]); EXPECT_EQ(THREAD_EVENT_MESSAGE_LOOP_DESTROYED, captured_events[2]); } + +TEST_F(ThreadTest, ThreadNotStarted) { + Thread a("Inert"); + EXPECT_EQ(nullptr, a.task_runner()); +}
diff --git a/threading/worker_pool.cc b/threading/worker_pool.cc index bc016ce..71b1a2b 100644 --- a/threading/worker_pool.cc +++ b/threading/worker_pool.cc
@@ -21,6 +21,7 @@ explicit PostTaskAndReplyWorkerPool(bool task_is_slow) : task_is_slow_(task_is_slow) { } + ~PostTaskAndReplyWorkerPool() override = default; private: bool PostTask(const tracked_objects::Location& from_here,
diff --git a/time/time_posix.cc b/time/time_posix.cc index 7826fc6..fc82c62 100644 --- a/time/time_posix.cc +++ b/time/time_posix.cc
@@ -17,7 +17,6 @@ #include "base/basictypes.h" #include "base/logging.h" -#include "base/port.h" #include "build/build_config.h" #if defined(OS_ANDROID)
diff --git a/time/time_win.cc b/time/time_win.cc index 9144483..e904460 100644 --- a/time/time_win.cc +++ b/time/time_win.cc
@@ -428,7 +428,7 @@ } void InitializeNowFunctionPointers() { - LARGE_INTEGER ticks_per_sec = {0}; + LARGE_INTEGER ticks_per_sec = {}; if (!QueryPerformanceFrequency(&ticks_per_sec)) ticks_per_sec.QuadPart = 0;
diff --git a/trace_event/BUILD.gn b/trace_event/BUILD.gn index e6392e2..663d7ba 100644 --- a/trace_event/BUILD.gn +++ b/trace_event/BUILD.gn
@@ -13,6 +13,7 @@ "memory_dump_manager.cc", "memory_dump_manager.h", "memory_dump_provider.h", + "memory_dump_request_args.cc", "memory_dump_request_args.h", "memory_dump_session_state.cc", "memory_dump_session_state.h", @@ -39,6 +40,8 @@ "trace_event_impl_constants.cc", "trace_event_memory.cc", "trace_event_memory.h", + "trace_event_memory_overhead.cc", + "trace_event_memory_overhead.h", "trace_event_synthetic_delay.cc", "trace_event_synthetic_delay.h", "trace_event_system_stats_monitor.cc",
diff --git a/trace_event/malloc_dump_provider.cc b/trace_event/malloc_dump_provider.cc index 92d513f..4304f28 100644 --- a/trace_event/malloc_dump_provider.cc +++ b/trace_event/malloc_dump_provider.cc
@@ -32,14 +32,13 @@ struct mallinfo info = mallinfo(); DCHECK_GE(info.arena + info.hblkhd, info.uordblks); - // When the system allocator is implemented by tcmalloc, the total physical + // When the system allocator is implemented by tcmalloc, the total heap // size is given by |arena| and |hblkhd| is 0. In case of Android's jemalloc // |arena| is 0 and the outer pages size is reported by |hblkhd|. In case of // dlmalloc the total is given by |arena| + |hblkhd|. // For more details see link: http://goo.gl/fMR8lF. MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc"); - outer_dump->AddScalar(MemoryAllocatorDump::kNameSize, - MemoryAllocatorDump::kUnitsBytes, + outer_dump->AddScalar("heap_virtual_size", MemoryAllocatorDump::kUnitsBytes, info.arena + info.hblkhd); // Total allocated space is given by |uordblks|.
diff --git a/trace_event/memory_allocator_dump.cc b/trace_event/memory_allocator_dump.cc index 5d11bb0..4037f94 100644 --- a/trace_event/memory_allocator_dump.cc +++ b/trace_event/memory_allocator_dump.cc
@@ -15,28 +15,7 @@ namespace base { namespace trace_event { -namespace { -// Returns the c-string pointer from a dictionary value without performing extra -// std::string copies. The ptr will be valid as long as the value exists. -bool GetDictionaryValueAsCStr(const DictionaryValue* dict_value, - const std::string& key, - const char** out_cstr) { - const Value* value = nullptr; - const StringValue* str_value = nullptr; - if (!dict_value->GetWithoutPathExpansion(key, &value)) - return false; - if (!value->GetAsString(&str_value)) - return false; - *out_cstr = str_value->GetString().c_str(); - return true; -} -} // namespace - -// TODO(primiano): remove kName{Inner,Outer}Size below after all the existing -// dump providers have been rewritten. const char MemoryAllocatorDump::kNameSize[] = "size"; -const char MemoryAllocatorDump::kNameInnerSize[] = "inner_size"; -const char MemoryAllocatorDump::kNameOuterSize[] = "outer_size"; const char MemoryAllocatorDump::kNameObjectsCount[] = "objects_count"; const char MemoryAllocatorDump::kTypeScalar[] = "scalar"; const char MemoryAllocatorDump::kTypeString[] = "string"; @@ -48,6 +27,7 @@ const MemoryAllocatorDumpGuid& guid) : absolute_name_(absolute_name), process_memory_dump_(process_memory_dump), + attributes_(new TracedValue), guid_(guid) { // The |absolute_name| cannot be empty. DCHECK(!absolute_name.empty()); @@ -73,73 +53,47 @@ "%d:%s", TraceLog::GetInstance()->process_id(), absolute_name.c_str()))) { + string_conversion_buffer_.reserve(16); } MemoryAllocatorDump::~MemoryAllocatorDump() { } -void MemoryAllocatorDump::Add(const std::string& name, - const char* type, - const char* units, - scoped_ptr<Value> value) { - scoped_ptr<DictionaryValue> attribute(new DictionaryValue()); - DCHECK(!attributes_.HasKey(name)); - attribute->SetStringWithoutPathExpansion("type", type); - attribute->SetStringWithoutPathExpansion("units", units); - attribute->SetWithoutPathExpansion("value", value.Pass()); - attributes_.SetWithoutPathExpansion(name, attribute.Pass()); -} - -bool MemoryAllocatorDump::Get(const std::string& name, - const char** out_type, - const char** out_units, - const Value** out_value) const { - const DictionaryValue* attribute = nullptr; - if (!attributes_.GetDictionaryWithoutPathExpansion(name, &attribute)) - return false; - - if (!GetDictionaryValueAsCStr(attribute, "type", out_type)) - return false; - - if (!GetDictionaryValueAsCStr(attribute, "units", out_units)) - return false; - - if (!attribute->GetWithoutPathExpansion("value", out_value)) - return false; - - return true; -} - -void MemoryAllocatorDump::AddScalar(const std::string& name, +void MemoryAllocatorDump::AddScalar(const char* name, const char* units, uint64 value) { - scoped_ptr<Value> hex_value(new StringValue(StringPrintf("%" PRIx64, value))); - Add(name, kTypeScalar, units, hex_value.Pass()); + SStringPrintf(&string_conversion_buffer_, "%" PRIx64, value); + attributes_->BeginDictionary(name); + attributes_->SetString("type", kTypeScalar); + attributes_->SetString("units", units); + attributes_->SetString("value", string_conversion_buffer_); + attributes_->EndDictionary(); } -void MemoryAllocatorDump::AddScalarF(const std::string& name, +void MemoryAllocatorDump::AddScalarF(const char* name, const char* units, double value) { - Add(name, kTypeScalar, units, make_scoped_ptr(new FundamentalValue(value))); + attributes_->BeginDictionary(name); + attributes_->SetString("type", kTypeScalar); + attributes_->SetString("units", units); + attributes_->SetDouble("value", value); + attributes_->EndDictionary(); } -void MemoryAllocatorDump::AddString(const std::string& name, +void MemoryAllocatorDump::AddString(const char* name, const char* units, const std::string& value) { - scoped_ptr<Value> str_value(new StringValue(value)); - Add(name, kTypeString, units, str_value.Pass()); + attributes_->BeginDictionary(name); + attributes_->SetString("type", kTypeString); + attributes_->SetString("units", units); + attributes_->SetString("value", value); + attributes_->EndDictionary(); } void MemoryAllocatorDump::AsValueInto(TracedValue* value) const { - value->BeginDictionary(absolute_name_.c_str()); + value->BeginDictionaryWithCopiedName(absolute_name_); value->SetString("guid", guid_.ToString()); - - value->BeginDictionary("attrs"); - - for (DictionaryValue::Iterator it(attributes_); !it.IsAtEnd(); it.Advance()) - value->SetValue(it.key().c_str(), it.value().CreateDeepCopy()); - - value->EndDictionary(); // "attrs": { ... } + value->SetValue("attrs", *attributes_); value->EndDictionary(); // "allocator_name/heap_subheap": { ... } }
diff --git a/trace_event/memory_allocator_dump.h b/trace_event/memory_allocator_dump.h index 9a151a6..2ded173 100644 --- a/trace_event/memory_allocator_dump.h +++ b/trace_event/memory_allocator_dump.h
@@ -5,9 +5,12 @@ #ifndef BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_ #define BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_ +#include <string> + #include "base/base_export.h" #include "base/basictypes.h" #include "base/logging.h" +#include "base/memory/ref_counted.h" #include "base/trace_event/memory_allocator_dump_guid.h" #include "base/values.h" @@ -29,45 +32,32 @@ ProcessMemoryDump* process_memory_dump); ~MemoryAllocatorDump(); - // Standard attribute name to model allocated space. - static const char kNameSize[]; + // Standard attribute |name|s for the AddScalar and AddString() methods. + static const char kNameSize[]; // To represent allocated space. + static const char kNameObjectsCount[]; // To represent number of objects. - // Standard attribute name to model total space requested by the allocator - // (e.g., amount of pages requested to the system). - static const char kNameOuterSize[]; - - // Standard attribute name to model space for allocated objects, without - // taking into account allocator metadata or fragmentation. - static const char kNameInnerSize[]; - - // Standard attribute name to model the number of objects allocated. - static const char kNameObjectsCount[]; - - static const char kTypeScalar[]; // Type name for scalar attributes. - static const char kTypeString[]; // Type name for string attributes. + // Standard attribute |unit|s for the AddScalar and AddString() methods. static const char kUnitsBytes[]; // Unit name to represent bytes. static const char kUnitsObjects[]; // Unit name to represent #objects. + // Constants used only internally and by tests. + static const char kTypeScalar[]; // Type name for scalar attributes. + static const char kTypeString[]; // Type name for string attributes. + + // Setters for scalar attributes. Some examples: + // - "size" column (all dumps are expected to have at least this one): + // AddScalar(kNameSize, kUnitsBytes, 1234); + // - Some extra-column reporting internal details of the subsystem: + // AddScalar("number_of_freelist_entires", kUnitsObjects, 42) + // - Other informational column (will not be auto-added in the UI) + // AddScalarF("kittens_ratio", "ratio", 42.0f) + void AddScalar(const char* name, const char* units, uint64 value); + void AddScalarF(const char* name, const char* units, double value); + void AddString(const char* name, const char* units, const std::string& value); + // Absolute name, unique within the scope of an entire ProcessMemoryDump. const std::string& absolute_name() const { return absolute_name_; } - // Generic attribute setter / getter. - void Add(const std::string& name, - const char* type, - const char* units, - scoped_ptr<Value> value); - bool Get(const std::string& name, - const char** out_type, - const char** out_units, - const Value** out_value) const; - - // Helper setter for scalar attributes. - void AddScalar(const std::string& name, const char* units, uint64 value); - void AddScalarF(const std::string& name, const char* units, double value); - void AddString(const std::string& name, - const char* units, - const std::string& value); - // Called at trace generation time to populate the TracedValue. void AsValueInto(TracedValue* value) const; @@ -84,12 +74,18 @@ // expected to have the same guid. const MemoryAllocatorDumpGuid& guid() const { return guid_; } + TracedValue* attributes_for_testing() const { return attributes_.get(); } + private: const std::string absolute_name_; ProcessMemoryDump* const process_memory_dump_; // Not owned (PMD owns this). - DictionaryValue attributes_; + scoped_refptr<TracedValue> attributes_; MemoryAllocatorDumpGuid guid_; + // A local buffer for Sprintf conversion on fastpath. Avoids allocating + // temporary strings on each AddScalar() call. + std::string string_conversion_buffer_; + DISALLOW_COPY_AND_ASSIGN(MemoryAllocatorDump); };
diff --git a/trace_event/memory_allocator_dump_guid.cc b/trace_event/memory_allocator_dump_guid.cc index a4ea50d..69c7de6 100644 --- a/trace_event/memory_allocator_dump_guid.cc +++ b/trace_event/memory_allocator_dump_guid.cc
@@ -5,12 +5,21 @@ #include "base/trace_event/memory_allocator_dump_guid.h" #include "base/format_macros.h" -#include "base/hash.h" +#include "base/sha1.h" #include "base/strings/stringprintf.h" namespace base { namespace trace_event { +namespace { +uint64 HashString(const std::string& str) { + uint64 hash[(kSHA1Length + sizeof(uint64) - 1) / sizeof(uint64)] = {0}; + SHA1HashBytes(reinterpret_cast<const unsigned char*>(str.data()), str.size(), + reinterpret_cast<unsigned char*>(hash)); + return hash[0]; +} +} // namespace + MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid(uint64 guid) : guid_(guid) { } @@ -19,8 +28,7 @@ } MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid(const std::string& guid_str) - : MemoryAllocatorDumpGuid(Hash(guid_str)) { -} + : MemoryAllocatorDumpGuid(HashString(guid_str)) {} std::string MemoryAllocatorDumpGuid::ToString() const { return StringPrintf("%" PRIx64, guid_);
diff --git a/trace_event/memory_allocator_dump_guid.h b/trace_event/memory_allocator_dump_guid.h index 84c12ef..634ca81 100644 --- a/trace_event/memory_allocator_dump_guid.h +++ b/trace_event/memory_allocator_dump_guid.h
@@ -23,6 +23,8 @@ // global scope of all the traced processes. explicit MemoryAllocatorDumpGuid(const std::string& guid_str); + uint64 ToUint64() const { return guid_; } + // Returns a (hex-encoded) string representation of the guid. std::string ToString() const;
diff --git a/trace_event/memory_allocator_dump_unittest.cc b/trace_event/memory_allocator_dump_unittest.cc index b9adbae..85b98d6 100644 --- a/trace_event/memory_allocator_dump_unittest.cc +++ b/trace_event/memory_allocator_dump_unittest.cc
@@ -11,6 +11,7 @@ #include "base/trace_event/memory_dump_session_state.h" #include "base/trace_event/process_memory_dump.h" #include "base/trace_event/trace_event_argument.h" +#include "base/values.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { @@ -47,20 +48,23 @@ } }; -bool CheckAttribute(const MemoryAllocatorDump* dump, - const std::string& name, - const char* expected_type, - const char* expected_units, - const Value** out_value) { - const char* attr_type; - const char* attr_units; - bool res = dump->Get(name, &attr_type, &attr_units, out_value); - EXPECT_TRUE(res); - if (!res) - return false; - EXPECT_EQ(expected_type, std::string(attr_type)); - EXPECT_EQ(expected_units, std::string(attr_units)); - return true; +scoped_ptr<Value> CheckAttribute(const MemoryAllocatorDump* dump, + const std::string& name, + const char* expected_type, + const char* expected_units) { + scoped_ptr<Value> raw_attrs = dump->attributes_for_testing()->ToBaseValue(); + DictionaryValue* args = nullptr; + DictionaryValue* arg = nullptr; + std::string arg_value; + const Value* out_value = nullptr; + EXPECT_TRUE(raw_attrs->GetAsDictionary(&args)); + EXPECT_TRUE(args->GetDictionary(name, &arg)); + EXPECT_TRUE(arg->GetString("type", &arg_value)); + EXPECT_EQ(expected_type, arg_value); + EXPECT_TRUE(arg->GetString("units", &arg_value)); + EXPECT_EQ(expected_units, arg_value); + EXPECT_TRUE(arg->Get("value", &out_value)); + return out_value ? out_value->CreateDeepCopy() : scoped_ptr<Value>(); } void CheckString(const MemoryAllocatorDump* dump, @@ -68,12 +72,8 @@ const char* expected_type, const char* expected_units, const std::string& expected_value) { - const Value* attr_value = nullptr; std::string attr_str_value; - bool res = - CheckAttribute(dump, name, expected_type, expected_units, &attr_value); - if (!res) - return; + auto attr_value = CheckAttribute(dump, name, expected_type, expected_units); EXPECT_TRUE(attr_value->GetAsString(&attr_str_value)); EXPECT_EQ(expected_value, attr_str_value); } @@ -90,12 +90,9 @@ const std::string& name, const char* expected_units, double expected_value) { - const Value* attr_value = nullptr; + auto attr_value = CheckAttribute(dump, name, MemoryAllocatorDump::kTypeScalar, + expected_units); double attr_double_value; - bool res = CheckAttribute(dump, name, MemoryAllocatorDump::kTypeScalar, - expected_units, &attr_value); - if (!res) - return; EXPECT_TRUE(attr_value->GetAsDouble(&attr_double_value)); EXPECT_EQ(expected_value, attr_double_value); } @@ -154,15 +151,15 @@ MemoryAllocatorDump::kUnitsBytes, 1); CheckScalar(sub_heap, MemoryAllocatorDump::kNameObjectsCount, MemoryAllocatorDump::kUnitsObjects, 3); - const MemoryAllocatorDump* empty_sub_heap = pmd.GetAllocatorDump("foobar_allocator/sub_heap/empty"); ASSERT_NE(nullptr, empty_sub_heap); EXPECT_EQ("foobar_allocator/sub_heap/empty", empty_sub_heap->absolute_name()); - ASSERT_FALSE(empty_sub_heap->Get(MemoryAllocatorDump::kNameSize, nullptr, - nullptr, nullptr)); - ASSERT_FALSE(empty_sub_heap->Get(MemoryAllocatorDump::kNameObjectsCount, - nullptr, nullptr, nullptr)); + auto raw_attrs = empty_sub_heap->attributes_for_testing()->ToBaseValue(); + DictionaryValue* attrs = nullptr; + ASSERT_TRUE(raw_attrs->GetAsDictionary(&attrs)); + ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameSize)); + ASSERT_FALSE(attrs->HasKey(MemoryAllocatorDump::kNameObjectsCount)); // Check that the AsValueInfo doesn't hit any DCHECK. scoped_refptr<TracedValue> traced_value(new TracedValue());
diff --git a/trace_event/memory_dump_manager.cc b/trace_event/memory_dump_manager.cc index 15abb91..e2ca702 100644 --- a/trace_event/memory_dump_manager.cc +++ b/trace_event/memory_dump_manager.cc
@@ -8,6 +8,8 @@ #include "base/atomic_sequence_num.h" #include "base/compiler_specific.h" +#include "base/hash.h" +#include "base/thread_task_runner_handle.h" #include "base/trace_event/memory_dump_provider.h" #include "base/trace_event/memory_dump_session_state.h" #include "base/trace_event/process_memory_dump.h" @@ -40,96 +42,26 @@ // trace event synthetic delays. const char kTraceCategory[] = TRACE_DISABLED_BY_DEFAULT("memory-infra"); -MemoryDumpManager* g_instance_for_testing = nullptr; -const int kDumpIntervalSeconds = 2; +// Throttle mmaps at a rate of once every kHeavyMmapsDumpsRate standard dumps. +const int kHeavyMmapsDumpsRate = 8; // 250 ms * 8 = 2000 ms. +const int kDumpIntervalMs = 250; const int kTraceEventNumArgs = 1; const char* kTraceEventArgNames[] = {"dumps"}; const unsigned char kTraceEventArgTypes[] = {TRACE_VALUE_TYPE_CONVERTABLE}; + StaticAtomicSequenceNumber g_next_guid; - -const char* MemoryDumpTypeToString(const MemoryDumpType& dump_type) { - switch (dump_type) { - case MemoryDumpType::TASK_BEGIN: - return "TASK_BEGIN"; - case MemoryDumpType::TASK_END: - return "TASK_END"; - case MemoryDumpType::PERIODIC_INTERVAL: - return "PERIODIC_INTERVAL"; - case MemoryDumpType::EXPLICITLY_TRIGGERED: - return "EXPLICITLY_TRIGGERED"; - } - NOTREACHED(); - return "UNKNOWN"; -} - -// Internal class used to hold details about ProcessMemoryDump requests for the -// current process. -// TODO(primiano): In the upcoming CLs, ProcessMemoryDump will become async. -// and this class will be used to convey more details across PostTask()s. -class ProcessMemoryDumpHolder - : public RefCountedThreadSafe<ProcessMemoryDumpHolder> { - public: - ProcessMemoryDumpHolder( - MemoryDumpRequestArgs req_args, - const scoped_refptr<MemoryDumpSessionState>& session_state, - MemoryDumpCallback callback) - : process_memory_dump(session_state), - req_args(req_args), - callback(callback), - task_runner(MessageLoop::current()->task_runner()), - num_pending_async_requests(0) {} - - ProcessMemoryDump process_memory_dump; - const MemoryDumpRequestArgs req_args; - - // Callback passed to the initial call to CreateProcessDump(). - MemoryDumpCallback callback; - - // Thread on which FinalizeDumpAndAddToTrace() should be called, which is the - // same that invoked the initial CreateProcessDump(). - const scoped_refptr<SingleThreadTaskRunner> task_runner; - - // Number of pending ContinueAsyncProcessDump() calls. - int num_pending_async_requests; - - private: - friend class RefCountedThreadSafe<ProcessMemoryDumpHolder>; - virtual ~ProcessMemoryDumpHolder() {} - DISALLOW_COPY_AND_ASSIGN(ProcessMemoryDumpHolder); -}; - -void FinalizeDumpAndAddToTrace( - const scoped_refptr<ProcessMemoryDumpHolder>& pmd_holder) { - DCHECK_EQ(0, pmd_holder->num_pending_async_requests); - - if (!pmd_holder->task_runner->BelongsToCurrentThread()) { - pmd_holder->task_runner->PostTask( - FROM_HERE, Bind(&FinalizeDumpAndAddToTrace, pmd_holder)); - return; - } - - scoped_refptr<ConvertableToTraceFormat> event_value(new TracedValue()); - pmd_holder->process_memory_dump.AsValueInto( - static_cast<TracedValue*>(event_value.get())); - const char* const event_name = - MemoryDumpTypeToString(pmd_holder->req_args.dump_type); - - TRACE_EVENT_API_ADD_TRACE_EVENT( - TRACE_EVENT_PHASE_MEMORY_DUMP, - TraceLog::GetCategoryGroupEnabled(kTraceCategory), event_name, - pmd_holder->req_args.dump_guid, kTraceEventNumArgs, kTraceEventArgNames, - kTraceEventArgTypes, nullptr /* arg_values */, &event_value, - TRACE_EVENT_FLAG_HAS_ID); - - if (!pmd_holder->callback.is_null()) { - pmd_holder->callback.Run(pmd_holder->req_args.dump_guid, true); - pmd_holder->callback.Reset(); - } -} +uint32 g_periodic_dumps_count = 0; +MemoryDumpManager* g_instance_for_testing = nullptr; +MemoryDumpProvider* g_mmaps_dump_provider = nullptr; void RequestPeriodicGlobalDump() { - MemoryDumpManager::GetInstance()->RequestGlobalDump( - MemoryDumpType::PERIODIC_INTERVAL); + MemoryDumpType dump_type = g_periodic_dumps_count == 0 + ? MemoryDumpType::PERIODIC_INTERVAL_WITH_MMAPS + : MemoryDumpType::PERIODIC_INTERVAL; + if (++g_periodic_dumps_count == kHeavyMmapsDumpsRate) + g_periodic_dumps_count = 0; + + MemoryDumpManager::GetInstance()->RequestGlobalDump(dump_type); } } // namespace @@ -138,6 +70,12 @@ const char* const MemoryDumpManager::kTraceCategoryForTesting = kTraceCategory; // static +const uint64 MemoryDumpManager::kInvalidTracingProcessId = 0; + +// static +const int MemoryDumpManager::kMaxConsecutiveFailuresCount = 3; + +// static MemoryDumpManager* MemoryDumpManager::GetInstance() { if (g_instance_for_testing) return g_instance_for_testing; @@ -154,8 +92,10 @@ } MemoryDumpManager::MemoryDumpManager() - : delegate_(nullptr), + : did_unregister_dump_provider_(false), + delegate_(nullptr), memory_tracing_enabled_(0), + tracing_process_id_(kInvalidTracingProcessId), skip_core_dumpers_auto_registration_for_testing_(false) { g_next_guid.GetNext(); // Make sure that first guid is not zero. } @@ -177,7 +117,8 @@ #endif #if (defined(OS_LINUX) && !defined(FNL_MUSL)) || defined(OS_ANDROID) - RegisterDumpProvider(ProcessMemoryMapsDumpProvider::GetInstance()); + g_mmaps_dump_provider = ProcessMemoryMapsDumpProvider::GetInstance(); + RegisterDumpProvider(g_mmaps_dump_provider); RegisterDumpProvider(MallocDumpProvider::GetInstance()); #endif @@ -199,9 +140,9 @@ void MemoryDumpManager::RegisterDumpProvider( MemoryDumpProvider* mdp, const scoped_refptr<SingleThreadTaskRunner>& task_runner) { - MemoryDumpProviderInfo mdp_info(task_runner); + MemoryDumpProviderInfo mdp_info(mdp, task_runner); AutoLock lock(lock_); - dump_providers_.insert(std::make_pair(mdp, mdp_info)); + dump_providers_.insert(mdp_info); } void MemoryDumpManager::RegisterDumpProvider(MemoryDumpProvider* mdp) { @@ -211,11 +152,15 @@ void MemoryDumpManager::UnregisterDumpProvider(MemoryDumpProvider* mdp) { AutoLock lock(lock_); - auto it = dump_providers_.find(mdp); - if (it == dump_providers_.end()) + auto mdp_iter = dump_providers_.begin(); + for (; mdp_iter != dump_providers_.end(); ++mdp_iter) { + if (mdp_iter->dump_provider == mdp) + break; + } + + if (mdp_iter == dump_providers_.end()) return; - const MemoryDumpProviderInfo& mdp_info = it->second; // Unregistration of a MemoryDumpProvider while tracing is ongoing is safe // only if the MDP has specified a thread affinity (via task_runner()) AND // the unregistration happens on the same thread (so the MDP cannot unregister @@ -224,13 +169,12 @@ // race-free. If you hit this DCHECK, your MDP has a bug. DCHECK_IMPLIES( subtle::NoBarrier_Load(&memory_tracing_enabled_), - mdp_info.task_runner && mdp_info.task_runner->BelongsToCurrentThread()) + mdp_iter->task_runner && mdp_iter->task_runner->BelongsToCurrentThread()) << "The MemoryDumpProvider attempted to unregister itself in a racy way. " - << " Please file a crbug."; + << "Please file a crbug."; - // Remove from the enabled providers list. This is to deal with the case that - // UnregisterDumpProvider is called while the trace is enabled. - dump_providers_.erase(it); + dump_providers_.erase(mdp_iter); + did_unregister_dump_provider_ = true; } void MemoryDumpManager::RequestGlobalDump( @@ -265,96 +209,179 @@ RequestGlobalDump(dump_type, MemoryDumpCallback()); } -// Creates a memory dump for the current process and appends it to the trace. void MemoryDumpManager::CreateProcessDump(const MemoryDumpRequestArgs& args, const MemoryDumpCallback& callback) { - scoped_refptr<ProcessMemoryDumpHolder> pmd_holder( - new ProcessMemoryDumpHolder(args, session_state_, callback)); - ProcessMemoryDump* pmd = &pmd_holder->process_memory_dump; - bool did_any_provider_dump = false; - - // Iterate over the active dump providers and invoke OnMemoryDump(pmd). - // The MDM guarantees linearity (at most one MDP is active within one - // process) and thread-safety (MDM enforces the right locking when entering / - // leaving the MDP.OnMemoryDump() call). This is to simplify the clients' - // design - // and not let the MDPs worry about locking. - // As regards thread affinity, depending on the MDP configuration (see - // memory_dump_provider.h), the OnMemoryDump() invocation can happen: - // - Synchronousy on the MDM thread, when MDP.task_runner() is not set. - // - Posted on MDP.task_runner(), when MDP.task_runner() is set. + scoped_ptr<ProcessMemoryDumpAsyncState> pmd_async_state; { AutoLock lock(lock_); - for (auto it = dump_providers_.begin(); it != dump_providers_.end(); ++it) { - MemoryDumpProvider* mdp = it->first; - MemoryDumpProviderInfo* mdp_info = &it->second; - if (mdp_info->disabled) - continue; - if (mdp_info->task_runner) { - // The OnMemoryDump() call must be posted. - bool did_post_async_task = mdp_info->task_runner->PostTask( - FROM_HERE, Bind(&MemoryDumpManager::ContinueAsyncProcessDump, - Unretained(this), Unretained(mdp), pmd_holder)); - // The thread underlying the TaskRunner might have gone away. - if (did_post_async_task) - ++pmd_holder->num_pending_async_requests; - } else { - // Invoke the dump provider synchronously. - did_any_provider_dump |= InvokeDumpProviderLocked(mdp, pmd); - } - } - } // AutoLock - - // If at least one synchronous provider did dump and there are no pending - // asynchronous requests, add the dump to the trace and invoke the callback - // straight away (FinalizeDumpAndAddToTrace() takes care of the callback). - if (did_any_provider_dump && pmd_holder->num_pending_async_requests == 0) - FinalizeDumpAndAddToTrace(pmd_holder); -} - -// Invokes the MemoryDumpProvider.OnMemoryDump(), taking care of the fail-safe -// logic which disables the dumper when failing (crbug.com/461788). -bool MemoryDumpManager::InvokeDumpProviderLocked(MemoryDumpProvider* mdp, - ProcessMemoryDump* pmd) { - lock_.AssertAcquired(); - bool dump_successful = mdp->OnMemoryDump(pmd); - if (!dump_successful) { - LOG(ERROR) << "The memory dumper failed, possibly due to sandboxing " - "(crbug.com/461788), disabling it for current process. Try " - "restarting chrome with the --no-sandbox switch."; - dump_providers_.find(mdp)->second.disabled = true; + did_unregister_dump_provider_ = false; + pmd_async_state.reset(new ProcessMemoryDumpAsyncState( + args, dump_providers_.begin(), session_state_, callback)); } - return dump_successful; + + // Start the thread hop. |dump_providers_| are kept sorted by thread, so + // ContinueAsyncProcessDump will hop at most once per thread (w.r.t. thread + // affinity specified by the MemoryDumpProvider(s) in RegisterDumpProvider()). + ContinueAsyncProcessDump(pmd_async_state.Pass()); } -// This is posted to arbitrary threads as a continuation of CreateProcessDump(), -// when one or more MemoryDumpProvider(s) require the OnMemoryDump() call to -// happen on a different thread. +// At most one ContinueAsyncProcessDump() can be active at any time for a given +// PMD, regardless of status of the |lock_|. |lock_| is used here purely to +// ensure consistency w.r.t. (un)registrations of |dump_providers_|. +// The linearization of dump providers' OnMemoryDump invocations is achieved by +// means of subsequent PostTask(s). +// +// 1) Prologue: +// - Check if the dump provider is disabled, if so skip the dump. +// - Check if we are on the right thread. If not hop and continue there. +// 2) Invoke the dump provider's OnMemoryDump() (unless skipped). +// 3) Epilogue: +// - Unregister the dump provider if it failed too many times consecutively. +// - Advance the |next_dump_provider| iterator to the next dump provider. +// - If this was the last hop, create a trace event, add it to the trace +// and finalize (invoke callback). + void MemoryDumpManager::ContinueAsyncProcessDump( - MemoryDumpProvider* mdp, - scoped_refptr<ProcessMemoryDumpHolder> pmd_holder) { - bool should_finalize_dump = false; + scoped_ptr<ProcessMemoryDumpAsyncState> pmd_async_state) { + // Initalizes the ThreadLocalEventBuffer to guarantee that the TRACE_EVENTs + // in the PostTask below don't end up registering their own dump providers + // (for discounting trace memory overhead) while holding the |lock_|. + TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported(); + + // DO NOT put any LOG() statement in the locked sections, as in some contexts + // (GPU process) LOG() ends up performing PostTask/IPCs. + MemoryDumpProvider* mdp; + bool skip_dump = false; { - // The lock here is to guarantee that different asynchronous dumps on - // different threads are still serialized, so that the MemoryDumpProvider - // has a consistent view of the |pmd| argument passed. AutoLock lock(lock_); - ProcessMemoryDump* pmd = &pmd_holder->process_memory_dump; + // In the unlikely event that a dump provider was unregistered while + // dumping, abort the dump, as that would make |next_dump_provider| invalid. + // Registration, on the other hand, is safe as per std::set<> contract. + if (did_unregister_dump_provider_) { + return AbortDumpLocked(pmd_async_state->callback, + pmd_async_state->task_runner, + pmd_async_state->req_args.dump_guid); + } - // Check if the MemoryDumpProvider is still there. It might have been - // destroyed and unregistered while hopping threads. - if (dump_providers_.count(mdp)) - InvokeDumpProviderLocked(mdp, pmd); + auto* mdp_info = &*pmd_async_state->next_dump_provider; + mdp = mdp_info->dump_provider; + if (mdp_info->disabled) { + skip_dump = true; + } else if (mdp == g_mmaps_dump_provider && + pmd_async_state->req_args.dump_type != + MemoryDumpType::PERIODIC_INTERVAL_WITH_MMAPS) { + // Mmaps dumping is very heavyweight and cannot be performed at the same + // rate of other dumps. TODO(primiano): this is a hack and should be + // cleaned up as part of crbug.com/499731. + skip_dump = true; + } else if (mdp_info->task_runner && + !mdp_info->task_runner->BelongsToCurrentThread()) { + // It's time to hop onto another thread. - // Finalize the dump appending it to the trace if this was the last - // asynchronous request pending. - --pmd_holder->num_pending_async_requests; - if (pmd_holder->num_pending_async_requests == 0) - should_finalize_dump = true; + // Copy the callback + arguments just for the unlikley case in which + // PostTask fails. In such case the Bind helper will destroy the + // pmd_async_state and we must keep a copy of the fields to notify the + // abort. + MemoryDumpCallback callback = pmd_async_state->callback; + scoped_refptr<SingleThreadTaskRunner> callback_task_runner = + pmd_async_state->task_runner; + const uint64 dump_guid = pmd_async_state->req_args.dump_guid; + + const bool did_post_task = mdp_info->task_runner->PostTask( + FROM_HERE, Bind(&MemoryDumpManager::ContinueAsyncProcessDump, + Unretained(this), Passed(pmd_async_state.Pass()))); + if (did_post_task) + return; + + // The thread is gone. At this point the best thing we can do is to + // disable the dump provider and abort this dump. + mdp_info->disabled = true; + return AbortDumpLocked(callback, callback_task_runner, dump_guid); + } } // AutoLock(lock_) - if (should_finalize_dump) - FinalizeDumpAndAddToTrace(pmd_holder); + // Invoke the dump provider without holding the |lock_|. + bool finalize = false; + bool dump_successful = false; + if (!skip_dump) + dump_successful = mdp->OnMemoryDump(&pmd_async_state->process_memory_dump); + + { + AutoLock lock(lock_); + if (did_unregister_dump_provider_) { + return AbortDumpLocked(pmd_async_state->callback, + pmd_async_state->task_runner, + pmd_async_state->req_args.dump_guid); + } + auto* mdp_info = &*pmd_async_state->next_dump_provider; + if (dump_successful) { + mdp_info->consecutive_failures = 0; + } else if (!skip_dump) { + ++mdp_info->consecutive_failures; + if (mdp_info->consecutive_failures >= kMaxConsecutiveFailuresCount) { + mdp_info->disabled = true; + } + } + ++pmd_async_state->next_dump_provider; + finalize = pmd_async_state->next_dump_provider == dump_providers_.end(); + } + + if (!skip_dump && !dump_successful) { + LOG(ERROR) << "A memory dumper failed, possibly due to sandboxing " + "(crbug.com/461788). Disabling dumper for current process. " + "Try restarting chrome with the --no-sandbox switch."; + } + + if (finalize) + return FinalizeDumpAndAddToTrace(pmd_async_state.Pass()); + + ContinueAsyncProcessDump(pmd_async_state.Pass()); +} + +// static +void MemoryDumpManager::FinalizeDumpAndAddToTrace( + scoped_ptr<ProcessMemoryDumpAsyncState> pmd_async_state) { + if (!pmd_async_state->task_runner->BelongsToCurrentThread()) { + scoped_refptr<SingleThreadTaskRunner> task_runner = + pmd_async_state->task_runner; + task_runner->PostTask(FROM_HERE, + Bind(&MemoryDumpManager::FinalizeDumpAndAddToTrace, + Passed(pmd_async_state.Pass()))); + return; + } + + scoped_refptr<ConvertableToTraceFormat> event_value(new TracedValue()); + pmd_async_state->process_memory_dump.AsValueInto( + static_cast<TracedValue*>(event_value.get())); + const char* const event_name = + MemoryDumpTypeToString(pmd_async_state->req_args.dump_type); + + TRACE_EVENT_API_ADD_TRACE_EVENT( + TRACE_EVENT_PHASE_MEMORY_DUMP, + TraceLog::GetCategoryGroupEnabled(kTraceCategory), event_name, + pmd_async_state->req_args.dump_guid, kTraceEventNumArgs, + kTraceEventArgNames, kTraceEventArgTypes, nullptr /* arg_values */, + &event_value, TRACE_EVENT_FLAG_HAS_ID); + + if (!pmd_async_state->callback.is_null()) { + pmd_async_state->callback.Run(pmd_async_state->req_args.dump_guid, + true /* success */); + pmd_async_state->callback.Reset(); + } +} + +// static +void MemoryDumpManager::AbortDumpLocked( + MemoryDumpCallback callback, + scoped_refptr<SingleThreadTaskRunner> task_runner, + uint64 dump_guid) { + if (callback.is_null()) + return; // There is nothing to NACK. + + // Post the callback even if we are already on the right thread to avoid + // invoking the callback while holding the lock_. + task_runner->PostTask(FROM_HERE, + Bind(callback, dump_guid, false /* success */)); } void MemoryDumpManager::OnTraceLogEnabled() { @@ -364,25 +391,33 @@ bool enabled; TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &enabled); + // Initialize the TraceLog for the current thread. This is to avoid that the + // TraceLog memory dump provider is registered lazily in the PostTask() below + // while the |lock_| is taken; + TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported(); + AutoLock lock(lock_); // There is no point starting the tracing without a delegate. if (!enabled || !delegate_) { // Disable all the providers. for (auto it = dump_providers_.begin(); it != dump_providers_.end(); ++it) - it->second.disabled = true; + it->disabled = true; return; } session_state_ = new MemoryDumpSessionState(); - for (auto it = dump_providers_.begin(); it != dump_providers_.end(); ++it) - it->second.disabled = false; + for (auto it = dump_providers_.begin(); it != dump_providers_.end(); ++it) { + it->disabled = false; + it->consecutive_failures = 0; + } subtle::NoBarrier_Store(&memory_tracing_enabled_, 1); if (delegate_->IsCoordinatorProcess()) { + g_periodic_dumps_count = 0; periodic_dump_timer_.Start(FROM_HERE, - TimeDelta::FromSeconds(kDumpIntervalSeconds), + TimeDelta::FromMilliseconds(kDumpIntervalMs), base::Bind(&RequestPeriodicGlobalDump)); } } @@ -394,12 +429,46 @@ session_state_ = nullptr; } -MemoryDumpManager::MemoryDumpProviderInfo::MemoryDumpProviderInfo( - const scoped_refptr<SingleThreadTaskRunner>& task_runner) - : task_runner(task_runner), disabled(false) { +// static +uint64 MemoryDumpManager::ChildProcessIdToTracingProcessId( + int child_process_id) { + return static_cast<uint64>( + Hash(reinterpret_cast<const char*>(&child_process_id), + sizeof(child_process_id))) + + 1; } + +MemoryDumpManager::MemoryDumpProviderInfo::MemoryDumpProviderInfo( + MemoryDumpProvider* dump_provider, + const scoped_refptr<SingleThreadTaskRunner>& task_runner) + : dump_provider(dump_provider), + task_runner(task_runner), + consecutive_failures(0), + disabled(false) {} + MemoryDumpManager::MemoryDumpProviderInfo::~MemoryDumpProviderInfo() { } +bool MemoryDumpManager::MemoryDumpProviderInfo::operator<( + const MemoryDumpProviderInfo& other) const { + if (task_runner == other.task_runner) + return dump_provider < other.dump_provider; + return task_runner < other.task_runner; +} + +MemoryDumpManager::ProcessMemoryDumpAsyncState::ProcessMemoryDumpAsyncState( + MemoryDumpRequestArgs req_args, + MemoryDumpProviderInfoSet::iterator next_dump_provider, + const scoped_refptr<MemoryDumpSessionState>& session_state, + MemoryDumpCallback callback) + : process_memory_dump(session_state), + req_args(req_args), + next_dump_provider(next_dump_provider), + callback(callback), + task_runner(MessageLoop::current()->task_runner()) {} + +MemoryDumpManager::ProcessMemoryDumpAsyncState::~ProcessMemoryDumpAsyncState() { +} + } // namespace trace_event } // namespace base
diff --git a/trace_event/memory_dump_manager.h b/trace_event/memory_dump_manager.h index 3645ac1..f9ece6e 100644 --- a/trace_event/memory_dump_manager.h +++ b/trace_event/memory_dump_manager.h
@@ -5,7 +5,7 @@ #ifndef BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_ #define BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_ -#include <vector> +#include <set> #include "base/atomicops.h" #include "base/containers/hash_tables.h" @@ -14,6 +14,7 @@ #include "base/synchronization/lock.h" #include "base/timer/timer.h" #include "base/trace_event/memory_dump_request_args.h" +#include "base/trace_event/process_memory_dump.h" #include "base/trace_event/trace_event.h" namespace base { @@ -22,13 +23,8 @@ namespace trace_event { -namespace { -class ProcessMemoryDumpHolder; -} - class MemoryDumpManagerDelegate; class MemoryDumpProvider; -class ProcessMemoryDump; class MemoryDumpSessionState; // This is the interface exposed to the rest of the codebase to deal with @@ -36,6 +32,7 @@ // RequestDumpPoint(). The extension by Un(RegisterDumpProvider). class BASE_EXPORT MemoryDumpManager : public TraceLog::EnabledStateObserver { public: + static const uint64 kInvalidTracingProcessId; static const char* const kTraceCategoryForTesting; static MemoryDumpManager* GetInstance(); @@ -81,27 +78,97 @@ return session_state_; } + // Derives a tracing process id from a child process id. Child process ids + // cannot be used directly in tracing for security reasons (see: discussion in + // crrev.com/1173263004). This method is meant to be used when dumping + // cross-process shared memory from a process which knows the child process id + // of its endpoints. The value returned by this method is guaranteed to be + // equal to the value returned by tracing_process_id() in the corresponding + // child process. + // This will never return kInvalidTracingProcessId. + static uint64 ChildProcessIdToTracingProcessId(int child_id); + + // Returns a unique id for the current process. The id can be retrieved only + // by child processes and only when tracing is enabled. This is intended to + // express cross-process sharing of memory dumps on the child-process side, + // without having to know its own child process id. + uint64 tracing_process_id() const { return tracing_process_id_; } + private: - // Descriptor struct used to hold information about registered MDPs. It is - // deliberately copyable, in order to allow to be used as hash_map value. - struct MemoryDumpProviderInfo { - MemoryDumpProviderInfo( - const scoped_refptr<SingleThreadTaskRunner>& task_runner); - ~MemoryDumpProviderInfo(); - - scoped_refptr<SingleThreadTaskRunner> task_runner; // Optional. - bool disabled; // For fail-safe logic (auto-disable failing MDPs). - }; - friend struct DefaultDeleter<MemoryDumpManager>; // For the testing instance. friend struct DefaultSingletonTraits<MemoryDumpManager>; friend class MemoryDumpManagerDelegate; friend class MemoryDumpManagerTest; + FRIEND_TEST_ALL_PREFIXES(MemoryDumpManagerTest, DisableFailingDumpers); - static void SetInstanceForTesting(MemoryDumpManager* instance); + // Descriptor struct used to hold information about registered MDPs. It is + // deliberately copyable, in order to allow it to be used as std::set value. + struct MemoryDumpProviderInfo { + MemoryDumpProviderInfo( + MemoryDumpProvider* dump_provider, + const scoped_refptr<SingleThreadTaskRunner>& task_runner); + ~MemoryDumpProviderInfo(); + + // Define a total order based on the thread (i.e. |task_runner|) affinity, + // so that all MDP belonging to the same thread are adjacent in the set. + bool operator<(const MemoryDumpProviderInfo& other) const; + + MemoryDumpProvider* const dump_provider; + scoped_refptr<SingleThreadTaskRunner> task_runner; // Optional. + + // For fail-safe logic (auto-disable failing MDPs). These fields are mutable + // as can be safely changed without impacting the order within the set. + mutable int consecutive_failures; + mutable bool disabled; + }; + + using MemoryDumpProviderInfoSet = std::set<MemoryDumpProviderInfo>; + + // Holds the state of a process memory dump that needs to be carried over + // across threads in order to fulfil an asynchronous CreateProcessDump() + // request. At any time exactly one thread owns a ProcessMemoryDumpAsyncState. + struct ProcessMemoryDumpAsyncState { + ProcessMemoryDumpAsyncState( + MemoryDumpRequestArgs req_args, + MemoryDumpProviderInfoSet::iterator next_dump_provider, + const scoped_refptr<MemoryDumpSessionState>& session_state, + MemoryDumpCallback callback); + ~ProcessMemoryDumpAsyncState(); + + // The ProcessMemoryDump container, where each dump provider will dump its + // own MemoryAllocatorDump(s) upon the OnMemoryDump() call. + ProcessMemoryDump process_memory_dump; + + // The arguments passed to the initial CreateProcessDump() request. + const MemoryDumpRequestArgs req_args; + + // The |dump_providers_| iterator to the next dump provider that should be + // invoked (or dump_providers_.end() if at the end of the sequence). + MemoryDumpProviderInfoSet::iterator next_dump_provider; + + // Callback passed to the initial call to CreateProcessDump(). + MemoryDumpCallback callback; + + // The thread on which FinalizeDumpAndAddToTrace() (and hence |callback|) + // should be invoked. This is the thread on which the initial + // CreateProcessDump() request was called. + const scoped_refptr<SingleThreadTaskRunner> task_runner; + + private: + DISALLOW_COPY_AND_ASSIGN(ProcessMemoryDumpAsyncState); + }; + + static const int kMaxConsecutiveFailuresCount; MemoryDumpManager(); - virtual ~MemoryDumpManager(); + ~MemoryDumpManager() override; + + static void SetInstanceForTesting(MemoryDumpManager* instance); + static void FinalizeDumpAndAddToTrace( + scoped_ptr<ProcessMemoryDumpAsyncState> pmd_async_state); + static void AbortDumpLocked(MemoryDumpCallback callback, + scoped_refptr<SingleThreadTaskRunner> task_runner, + uint64 dump_guid); // Internal, used only by MemoryDumpManagerDelegate. // Creates a memory dump for the current process and appends it to the trace. @@ -110,13 +177,25 @@ void CreateProcessDump(const MemoryDumpRequestArgs& args, const MemoryDumpCallback& callback); - bool InvokeDumpProviderLocked(MemoryDumpProvider* mdp, - ProcessMemoryDump* pmd); + // Continues the ProcessMemoryDump started by CreateProcessDump(), hopping + // across threads as needed as specified by MDPs in RegisterDumpProvider(). void ContinueAsyncProcessDump( - MemoryDumpProvider* mdp, - scoped_refptr<ProcessMemoryDumpHolder> pmd_holder); + scoped_ptr<ProcessMemoryDumpAsyncState> pmd_async_state); - hash_map<MemoryDumpProvider*, MemoryDumpProviderInfo> dump_providers_; + // Pass kInvalidTracingProcessId to invalidate the id. + void set_tracing_process_id(uint64 id) { + DCHECK(tracing_process_id_ == kInvalidTracingProcessId || + id == kInvalidTracingProcessId || tracing_process_id_ == id); + tracing_process_id_ = id; + } + + // An ordererd set of registered MemoryDumpProviderInfo(s), sorted by thread + // affinity (MDPs belonging to the same thread are adjacent). + MemoryDumpProviderInfoSet dump_providers_; + + // Flag used to signal that some provider was removed from |dump_providers_| + // and therefore the current memory dump (if any) should be aborted. + bool did_unregister_dump_provider_; // Shared among all the PMDs to keep state scoped to the tracing session. scoped_refptr<MemoryDumpSessionState> session_state_; @@ -134,6 +213,10 @@ // For time-triggered periodic dumps. RepeatingTimer<MemoryDumpManager> periodic_dump_timer_; + // The unique id of the child process. This is created only for tracing and is + // expected to be valid only when tracing is enabled. + uint64 tracing_process_id_; + // Skips the auto-registration of the core dumpers during Initialize(). bool skip_core_dumpers_auto_registration_for_testing_; @@ -160,6 +243,10 @@ MemoryDumpManager::GetInstance()->CreateProcessDump(args, callback); } + void set_tracing_process_id(uint64 id) { + MemoryDumpManager::GetInstance()->set_tracing_process_id(id); + } + private: DISALLOW_COPY_AND_ASSIGN(MemoryDumpManagerDelegate); };
diff --git a/trace_event/memory_dump_manager_unittest.cc b/trace_event/memory_dump_manager_unittest.cc index c15748c..be20cec 100644 --- a/trace_event/memory_dump_manager_unittest.cc +++ b/trace_event/memory_dump_manager_unittest.cc
@@ -8,6 +8,7 @@ #include "base/memory/scoped_vector.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" +#include "base/thread_task_runner_handle.h" #include "base/threading/thread.h" #include "base/trace_event/memory_dump_provider.h" #include "base/trace_event/process_memory_dump.h" @@ -15,6 +16,7 @@ #include "testing/gtest/include/gtest/gtest.h" using testing::_; +using testing::Between; using testing::Invoke; using testing::Return; @@ -25,9 +27,8 @@ // instead of performing IPC dances. class MemoryDumpManagerDelegateForTesting : public MemoryDumpManagerDelegate { public: - void RequestGlobalMemoryDump( - const base::trace_event::MemoryDumpRequestArgs& args, - const MemoryDumpCallback& callback) override { + void RequestGlobalMemoryDump(const MemoryDumpRequestArgs& args, + const MemoryDumpCallback& callback) override { CreateProcessDump(args, callback); } @@ -81,7 +82,9 @@ class MockDumpProvider : public MemoryDumpProvider { public: - MockDumpProvider() : last_session_state_(nullptr) {} + MockDumpProvider() + : dump_provider_to_register_or_unregister(nullptr), + last_session_state_(nullptr) {} // Ctor used by the RespectTaskRunnerAffinity test. explicit MockDumpProvider( @@ -107,6 +110,23 @@ return true; } + // OnMemoryDump() override for the RegisterDumperWhileDumping test. + bool OnMemoryDump_RegisterExtraDumpProvider(ProcessMemoryDump* pmd) { + MemoryDumpManager::GetInstance()->RegisterDumpProvider( + dump_provider_to_register_or_unregister); + return true; + } + + // OnMemoryDump() override for the UnegisterDumperWhileDumping test. + bool OnMemoryDump_UnregisterDumpProvider(ProcessMemoryDump* pmd) { + MemoryDumpManager::GetInstance()->UnregisterDumpProvider( + dump_provider_to_register_or_unregister); + return true; + } + + // Used by OnMemoryDump_(Un)RegisterExtraDumpProvider. + MemoryDumpProvider* dump_provider_to_register_or_unregister; + private: MemoryDumpSessionState* last_session_state_; scoped_refptr<SingleThreadTaskRunner> task_runner_; @@ -250,9 +270,8 @@ DisableTracing(); } -// Enable both dump providers, make mdp1 fail and assert that only mdp2 is -// invoked the 2nd time. -// FIXME(primiano): remove once crbug.com/461788 gets fixed. +// Enable both dump providers, make sure that mdp gets disabled after 3 failures +// and not disabled after 1. TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) { MockDumpProvider mdp1; MockDumpProvider mdp2; @@ -261,13 +280,78 @@ mdm_->RegisterDumpProvider(&mdp2); EnableTracing(kTraceCategory); - EXPECT_CALL(mdp1, OnMemoryDump(_)).Times(1).WillRepeatedly(Return(false)); - EXPECT_CALL(mdp2, OnMemoryDump(_)).Times(1).WillRepeatedly(Return(true)); - mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED); + EXPECT_CALL(mdp1, OnMemoryDump(_)) + .Times(MemoryDumpManager::kMaxConsecutiveFailuresCount) + .WillRepeatedly(Return(false)); - EXPECT_CALL(mdp1, OnMemoryDump(_)).Times(0); - EXPECT_CALL(mdp2, OnMemoryDump(_)).Times(1).WillRepeatedly(Return(false)); - mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED); + EXPECT_CALL(mdp2, OnMemoryDump(_)) + .Times(1 + MemoryDumpManager::kMaxConsecutiveFailuresCount) + .WillOnce(Return(false)) + .WillRepeatedly(Return(true)); + for (int i = 0; i < 1 + MemoryDumpManager::kMaxConsecutiveFailuresCount; + i++) { + mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED); + } + + DisableTracing(); +} + +// Sneakily register an extra memory dump provider while an existing one is +// dumping and expect it to take part in the already active tracing session. +TEST_F(MemoryDumpManagerTest, RegisterDumperWhileDumping) { + MockDumpProvider mdp1; + MockDumpProvider mdp2; + + mdp1.dump_provider_to_register_or_unregister = &mdp2; + mdm_->RegisterDumpProvider(&mdp1); + EnableTracing(kTraceCategory); + + EXPECT_CALL(mdp1, OnMemoryDump(_)) + .Times(4) + .WillOnce(Return(true)) + .WillOnce(Invoke( + &mdp1, &MockDumpProvider::OnMemoryDump_RegisterExtraDumpProvider)) + .WillRepeatedly(Return(true)); + + // Depending on the insertion order (before or after mdp1), mdp2 might be + // called also immediately after it gets registered. + EXPECT_CALL(mdp2, OnMemoryDump(_)) + .Times(Between(2, 3)) + .WillRepeatedly(Return(true)); + + for (int i = 0; i < 4; i++) { + mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED); + } + + DisableTracing(); +} + +// Like the above, but suddenly unregister the dump provider. +TEST_F(MemoryDumpManagerTest, UnregisterDumperWhileDumping) { + MockDumpProvider mdp1; + MockDumpProvider mdp2; + + mdm_->RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get()); + mdm_->RegisterDumpProvider(&mdp2, ThreadTaskRunnerHandle::Get()); + mdp1.dump_provider_to_register_or_unregister = &mdp2; + EnableTracing(kTraceCategory); + + EXPECT_CALL(mdp1, OnMemoryDump(_)) + .Times(4) + .WillOnce(Return(true)) + .WillOnce( + Invoke(&mdp1, &MockDumpProvider::OnMemoryDump_UnregisterDumpProvider)) + .WillRepeatedly(Return(true)); + + // Depending on the insertion order (before or after mdp1), mdp2 might have + // been already called when OnMemoryDump_UnregisterDumpProvider happens. + EXPECT_CALL(mdp2, OnMemoryDump(_)) + .Times(Between(1, 2)) + .WillRepeatedly(Return(true)); + + for (int i = 0; i < 4; i++) { + mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED); + } DisableTracing(); }
diff --git a/trace_event/memory_dump_request_args.cc b/trace_event/memory_dump_request_args.cc new file mode 100644 index 0000000..196e98c --- /dev/null +++ b/trace_event/memory_dump_request_args.cc
@@ -0,0 +1,31 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/trace_event/memory_dump_request_args.h" + +#include "base/logging.h" + +namespace base { +namespace trace_event { + +// static +const char* MemoryDumpTypeToString(const MemoryDumpType& dump_type) { + switch (dump_type) { + case MemoryDumpType::TASK_BEGIN: + return "TASK_BEGIN"; + case MemoryDumpType::TASK_END: + return "TASK_END"; + case MemoryDumpType::PERIODIC_INTERVAL: + return "PERIODIC_INTERVAL"; + case MemoryDumpType::PERIODIC_INTERVAL_WITH_MMAPS: + return "PERIODIC_INTERVAL_WITH_MMAPS"; + case MemoryDumpType::EXPLICITLY_TRIGGERED: + return "EXPLICITLY_TRIGGERED"; + } + NOTREACHED(); + return "UNKNOWN"; +} + +} // namespace trace_event +} // namespace base
diff --git a/trace_event/memory_dump_request_args.h b/trace_event/memory_dump_request_args.h index 4d3763a..05d98be 100644 --- a/trace_event/memory_dump_request_args.h +++ b/trace_event/memory_dump_request_args.h
@@ -20,10 +20,15 @@ TASK_BEGIN, // Dumping memory at the beginning of a message-loop task. TASK_END, // Dumping memory at the ending of a message-loop task. PERIODIC_INTERVAL, // Dumping memory at periodic intervals. - EXPLICITLY_TRIGGERED, // Non maskable dump request. - LAST = EXPLICITLY_TRIGGERED // For IPC macros. + PERIODIC_INTERVAL_WITH_MMAPS, // As above but w/ heavyweight mmaps dumps. + // Temporary workaround for crbug.com/499731. + EXPLICITLY_TRIGGERED, // Non maskable dump request. + LAST = EXPLICITLY_TRIGGERED // For IPC macros. }; +// Returns the name in string for the dump type given. +BASE_EXPORT const char* MemoryDumpTypeToString(const MemoryDumpType& dump_type); + using MemoryDumpCallback = Callback<void(uint64 dump_guid, bool success)>; struct BASE_EXPORT MemoryDumpRequestArgs {
diff --git a/trace_event/process_memory_dump.h b/trace_event/process_memory_dump.h index 88ce28a..3b71a2c 100644 --- a/trace_event/process_memory_dump.h +++ b/trace_event/process_memory_dump.h
@@ -48,29 +48,6 @@ ProcessMemoryDump(const scoped_refptr<MemoryDumpSessionState>& session_state); ~ProcessMemoryDump(); - // Called at trace generation time to populate the TracedValue. - void AsValueInto(TracedValue* value) const; - - // Removes all the MemoryAllocatorDump(s) contained in this instance. This - // ProcessMemoryDump can be safely reused as if it was new once this returns. - void Clear(); - - // Merges all MemoryAllocatorDump(s) contained in |other| inside this - // ProcessMemoryDump, transferring their ownership to this instance. - // |other| will be an empty ProcessMemoryDump after this method returns. - // This is to allow dump providers to pre-populate ProcessMemoryDump instances - // and later move their contents into the ProcessMemoryDump passed as argument - // of the MemoryDumpProvider::OnMemoryDump(ProcessMemoryDump*) callback. - void TakeAllDumpsFrom(ProcessMemoryDump* other); - - ProcessMemoryTotals* process_totals() { return &process_totals_; } - bool has_process_totals() const { return has_process_totals_; } - void set_has_process_totals() { has_process_totals_ = true; } - - ProcessMemoryMaps* process_mmaps() { return &process_mmaps_; } - bool has_process_mmaps() const { return has_process_mmaps_; } - void set_has_process_mmaps() { has_process_mmaps_ = true; } - // Creates a new MemoryAllocatorDump with the given name and returns the // empty object back to the caller. // Arguments: @@ -80,7 +57,8 @@ // Leading or trailing slashes are not allowed. // guid: an optional identifier, unique among all processes within the // scope of a global dump. This is only relevant when using - // AddOwnershipEdge(). If omitted, it will be automatically generated. + // AddOwnershipEdge() to express memory sharing. If omitted, + // it will be automatically generated. // ProcessMemoryDump handles the memory ownership of its MemoryAllocatorDumps. MemoryAllocatorDump* CreateAllocatorDump(const std::string& absolute_name); MemoryAllocatorDump* CreateAllocatorDump(const std::string& absolute_name, @@ -131,6 +109,29 @@ return session_state_; } + // Removes all the MemoryAllocatorDump(s) contained in this instance. This + // ProcessMemoryDump can be safely reused as if it was new once this returns. + void Clear(); + + // Merges all MemoryAllocatorDump(s) contained in |other| inside this + // ProcessMemoryDump, transferring their ownership to this instance. + // |other| will be an empty ProcessMemoryDump after this method returns. + // This is to allow dump providers to pre-populate ProcessMemoryDump instances + // and later move their contents into the ProcessMemoryDump passed as argument + // of the MemoryDumpProvider::OnMemoryDump(ProcessMemoryDump*) callback. + void TakeAllDumpsFrom(ProcessMemoryDump* other); + + // Called at trace generation time to populate the TracedValue. + void AsValueInto(TracedValue* value) const; + + ProcessMemoryTotals* process_totals() { return &process_totals_; } + bool has_process_totals() const { return has_process_totals_; } + void set_has_process_totals() { has_process_totals_ = true; } + + ProcessMemoryMaps* process_mmaps() { return &process_mmaps_; } + bool has_process_mmaps() const { return has_process_mmaps_; } + void set_has_process_mmaps() { has_process_mmaps_ = true; } + private: void AddAllocatorDumpInternal(MemoryAllocatorDump* mad);
diff --git a/trace_event/trace_config.cc b/trace_event/trace_config.cc index ef9b892..2a15ec5 100644 --- a/trace_event/trace_config.cc +++ b/trace_event/trace_config.cc
@@ -6,6 +6,7 @@ #include "base/json/json_reader.h" #include "base/json/json_writer.h" +#include "base/strings/pattern.h" #include "base/strings/string_split.h" #include "base/strings/string_tokenizer.h" #include "base/strings/stringprintf.h" @@ -147,8 +148,8 @@ if (IsCategoryEnabled(category_group_token.c_str())) { return true; } - if (!MatchPattern(category_group_token.c_str(), - TRACE_DISABLED_BY_DEFAULT("*"))) + if (!base::MatchPattern(category_group_token.c_str(), + TRACE_DISABLED_BY_DEFAULT("*"))) had_enabled_by_default = true; } // Do a second pass to check for explicitly disabled categories @@ -160,7 +161,7 @@ for (StringList::const_iterator ci = excluded_categories_.begin(); ci != excluded_categories_.end(); ++ci) { - if (MatchPattern(category_group_token.c_str(), ci->c_str())) { + if (base::MatchPattern(category_group_token.c_str(), ci->c_str())) { // Current token of category_group_name is present in excluded_list. // Flag the exclusion and proceed further to check if any of the // remaining categories of category_group_name is not present in the @@ -515,17 +516,17 @@ for (ci = disabled_categories_.begin(); ci != disabled_categories_.end(); ++ci) { - if (MatchPattern(category_name, ci->c_str())) + if (base::MatchPattern(category_name, ci->c_str())) return true; } - if (MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*"))) + if (base::MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*"))) return false; for (ci = included_categories_.begin(); ci != included_categories_.end(); ++ci) { - if (MatchPattern(category_name, ci->c_str())) + if (base::MatchPattern(category_name, ci->c_str())) return true; }
diff --git a/trace_event/trace_event.h b/trace_event/trace_event.h index 397bafc..77ec1de 100644 --- a/trace_event/trace_event.h +++ b/trace_event/trace_event.h
@@ -852,6 +852,10 @@ #define TRACE_EVENT_FLOW_END0(category_group, name, id) \ INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ category_group, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0(category_group, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, category_group, \ + name, id, \ + TRACE_EVENT_FLAG_BIND_TO_ENCLOSING) #define TRACE_EVENT_FLOW_END1(category_group, name, id, arg1_name, arg1_val) \ INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ category_group, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) @@ -885,6 +889,14 @@ category_group, name, TRACE_ID_DONT_MANGLE(id), TRACE_EVENT_FLAG_NONE,\ "snapshot", snapshot) +#define TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID_AND_TIMESTAMP( \ + category_group, name, id, timestamp, snapshot) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ + TRACE_EVENT_PHASE_SNAPSHOT_OBJECT, category_group, name, \ + TRACE_ID_DONT_MANGLE(id), \ + static_cast<int>(base::PlatformThread::CurrentId()), timestamp, \ + TRACE_EVENT_FLAG_NONE, "snapshot", snapshot) + #define TRACE_EVENT_OBJECT_DELETED_WITH_ID(category_group, name, id) \ INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_DELETE_OBJECT, \ category_group, name, TRACE_ID_DONT_MANGLE(id), TRACE_EVENT_FLAG_NONE) @@ -954,7 +966,7 @@ // const char** arg_names, // const unsigned char* arg_types, // const unsigned long long* arg_values, -// unsigned char flags) +// unsigned int flags) #define TRACE_EVENT_API_ADD_TRACE_EVENT \ base::trace_event::TraceLog::GetInstance()->AddTraceEvent @@ -971,7 +983,7 @@ // const char** arg_names, // const unsigned char* arg_types, // const unsigned long long* arg_values, -// unsigned char flags) +// unsigned int flags) #define TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP \ base::trace_event::TraceLog::GetInstance() \ ->AddTraceEventWithThreadIdAndTimestamp @@ -1067,39 +1079,38 @@ // Implementation detail: internal macro to create static category and add // event if the category is enabled. -#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category_group, name, id, \ - flags, ...) \ - do { \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - unsigned char trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ - trace_event_internal::TraceID trace_event_trace_id( \ - id, &trace_event_flags); \ - trace_event_internal::AddTraceEvent( \ - phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ - name, trace_event_trace_id.data(), trace_event_flags, \ - ##__VA_ARGS__); \ - } \ - } while (0) +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category_group, name, id, \ + flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + unsigned int trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ + trace_event_internal::TraceID trace_event_trace_id(id, \ + &trace_event_flags); \ + trace_event_internal::AddTraceEvent( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + trace_event_trace_id.data(), trace_event_flags, ##__VA_ARGS__); \ + } \ + } while (0) // Implementation detail: internal macro to create static category and add // event if the category is enabled. -#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(phase, \ - category_group, name, id, thread_id, timestamp, flags, ...) \ - do { \ - INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ - unsigned char trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ - trace_event_internal::TraceID trace_event_trace_id( \ - id, &trace_event_flags); \ - trace_event_internal::AddTraceEventWithThreadIdAndTimestamp( \ - phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ - name, trace_event_trace_id.data(), \ - thread_id, base::TraceTicks::FromInternalValue(timestamp), \ - trace_event_flags | TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP, \ - ##__VA_ARGS__); \ - } \ - } while (0) +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \ + phase, category_group, name, id, thread_id, timestamp, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ + if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ + unsigned int trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ + trace_event_internal::TraceID trace_event_trace_id(id, \ + &trace_event_flags); \ + trace_event_internal::AddTraceEventWithThreadIdAndTimestamp( \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ + trace_event_trace_id.data(), thread_id, \ + base::TraceTicks::FromInternalValue(timestamp), \ + trace_event_flags | TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP, \ + ##__VA_ARGS__); \ + } \ + } while (0) // Notes regarding the following definitions: // New values can be added and propagated to third party libraries, but existing @@ -1130,17 +1141,19 @@ #define TRACE_EVENT_PHASE_MEMORY_DUMP ('v') // Flags for changing the behavior of TRACE_EVENT_API_ADD_TRACE_EVENT. -#define TRACE_EVENT_FLAG_NONE (static_cast<unsigned char>(0)) -#define TRACE_EVENT_FLAG_COPY (static_cast<unsigned char>(1 << 0)) -#define TRACE_EVENT_FLAG_HAS_ID (static_cast<unsigned char>(1 << 1)) -#define TRACE_EVENT_FLAG_MANGLE_ID (static_cast<unsigned char>(1 << 2)) -#define TRACE_EVENT_FLAG_SCOPE_OFFSET (static_cast<unsigned char>(1 << 3)) -#define TRACE_EVENT_FLAG_SCOPE_EXTRA (static_cast<unsigned char>(1 << 4)) -#define TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP (static_cast<unsigned char>(1 << 5)) -#define TRACE_EVENT_FLAG_ASYNC_TTS (static_cast<unsigned char>(1 << 6)) +#define TRACE_EVENT_FLAG_NONE (static_cast<unsigned int>(0)) +#define TRACE_EVENT_FLAG_COPY (static_cast<unsigned int>(1 << 0)) +#define TRACE_EVENT_FLAG_HAS_ID (static_cast<unsigned int>(1 << 1)) +#define TRACE_EVENT_FLAG_MANGLE_ID (static_cast<unsigned int>(1 << 2)) +#define TRACE_EVENT_FLAG_SCOPE_OFFSET (static_cast<unsigned int>(1 << 3)) +#define TRACE_EVENT_FLAG_SCOPE_EXTRA (static_cast<unsigned int>(1 << 4)) +#define TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP (static_cast<unsigned int>(1 << 5)) +#define TRACE_EVENT_FLAG_ASYNC_TTS (static_cast<unsigned int>(1 << 6)) +#define TRACE_EVENT_FLAG_BIND_TO_ENCLOSING (static_cast<unsigned int>(1 << 7)) -#define TRACE_EVENT_FLAG_SCOPE_MASK (static_cast<unsigned char>( \ - TRACE_EVENT_FLAG_SCOPE_OFFSET | TRACE_EVENT_FLAG_SCOPE_EXTRA)) +#define TRACE_EVENT_FLAG_SCOPE_MASK \ + (static_cast<unsigned int>(TRACE_EVENT_FLAG_SCOPE_OFFSET | \ + TRACE_EVENT_FLAG_SCOPE_EXTRA)) // Type values for identifying types in the TraceValue union. #define TRACE_VALUE_TYPE_BOOL (static_cast<unsigned char>(1)) @@ -1220,36 +1233,42 @@ private: unsigned long long data_; }; - TraceID(const void* id, unsigned char* flags) - : data_(static_cast<unsigned long long>( - reinterpret_cast<uintptr_t>(id))) { + TraceID(const void* id, unsigned int* flags) + : data_( + static_cast<unsigned long long>(reinterpret_cast<uintptr_t>(id))) { *flags |= TRACE_EVENT_FLAG_MANGLE_ID; } - TraceID(ForceMangle id, unsigned char* flags) : data_(id.data()) { + TraceID(ForceMangle id, unsigned int* flags) : data_(id.data()) { *flags |= TRACE_EVENT_FLAG_MANGLE_ID; } - TraceID(DontMangle id, unsigned char* flags) : data_(id.data()) { + TraceID(DontMangle id, unsigned int* flags) : data_(id.data()) {} + TraceID(unsigned long long id, unsigned int* flags) : data_(id) { + (void)flags; } - TraceID(unsigned long long id, unsigned char* flags) - : data_(id) { (void)flags; } - TraceID(unsigned long id, unsigned char* flags) - : data_(id) { (void)flags; } - TraceID(unsigned int id, unsigned char* flags) - : data_(id) { (void)flags; } - TraceID(unsigned short id, unsigned char* flags) - : data_(id) { (void)flags; } - TraceID(unsigned char id, unsigned char* flags) - : data_(id) { (void)flags; } - TraceID(long long id, unsigned char* flags) - : data_(static_cast<unsigned long long>(id)) { (void)flags; } - TraceID(long id, unsigned char* flags) - : data_(static_cast<unsigned long long>(id)) { (void)flags; } - TraceID(int id, unsigned char* flags) - : data_(static_cast<unsigned long long>(id)) { (void)flags; } - TraceID(short id, unsigned char* flags) - : data_(static_cast<unsigned long long>(id)) { (void)flags; } - TraceID(signed char id, unsigned char* flags) - : data_(static_cast<unsigned long long>(id)) { (void)flags; } + TraceID(unsigned long id, unsigned int* flags) : data_(id) { (void)flags; } + TraceID(unsigned int id, unsigned int* flags) : data_(id) { (void)flags; } + TraceID(unsigned short id, unsigned int* flags) : data_(id) { (void)flags; } + TraceID(unsigned char id, unsigned int* flags) : data_(id) { (void)flags; } + TraceID(long long id, unsigned int* flags) + : data_(static_cast<unsigned long long>(id)) { + (void)flags; + } + TraceID(long id, unsigned int* flags) + : data_(static_cast<unsigned long long>(id)) { + (void)flags; + } + TraceID(int id, unsigned int* flags) + : data_(static_cast<unsigned long long>(id)) { + (void)flags; + } + TraceID(short id, unsigned int* flags) + : data_(static_cast<unsigned long long>(id)) { + (void)flags; + } + TraceID(signed char id, unsigned int* flags) + : data_(static_cast<unsigned long long>(id)) { + (void)flags; + } unsigned long long data() const { return data_; } @@ -1380,7 +1399,7 @@ unsigned long long id, int thread_id, const base::TraceTicks& timestamp, - unsigned char flags, + unsigned int flags, const char* arg1_name, const scoped_refptr<base::trace_event::ConvertableToTraceFormat>& arg1_val) { @@ -1391,7 +1410,7 @@ num_args, &arg1_name, arg_types, NULL, &arg1_val, flags); } -template<class ARG1_TYPE> +template <class ARG1_TYPE> static inline base::trace_event::TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( char phase, @@ -1400,7 +1419,7 @@ unsigned long long id, int thread_id, const base::TraceTicks& timestamp, - unsigned char flags, + unsigned int flags, const char* arg1_name, const ARG1_TYPE& arg1_val, const char* arg2_name, @@ -1423,7 +1442,7 @@ num_args, arg_names, arg_types, arg_values, convertable_values, flags); } -template<class ARG2_TYPE> +template <class ARG2_TYPE> static inline base::trace_event::TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( char phase, @@ -1432,7 +1451,7 @@ unsigned long long id, int thread_id, const base::TraceTicks& timestamp, - unsigned char flags, + unsigned int flags, const char* arg1_name, const scoped_refptr<base::trace_event::ConvertableToTraceFormat>& arg1_val, const char* arg2_name, @@ -1463,7 +1482,7 @@ unsigned long long id, int thread_id, const base::TraceTicks& timestamp, - unsigned char flags, + unsigned int flags, const char* arg1_name, const scoped_refptr<base::trace_event::ConvertableToTraceFormat>& arg1_val, const char* arg2_name, @@ -1489,7 +1508,7 @@ unsigned long long id, int thread_id, const base::TraceTicks& timestamp, - unsigned char flags) { + unsigned int flags) { return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, kZeroNumArgs, NULL, NULL, NULL, NULL, flags); @@ -1500,14 +1519,14 @@ const unsigned char* category_group_enabled, const char* name, unsigned long long id, - unsigned char flags) { + unsigned int flags) { const int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); const base::TraceTicks now = base::TraceTicks::Now(); return AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, thread_id, now, flags); } -template<class ARG1_TYPE> +template <class ARG1_TYPE> static inline base::trace_event::TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( char phase, @@ -1516,7 +1535,7 @@ unsigned long long id, int thread_id, const base::TraceTicks& timestamp, - unsigned char flags, + unsigned int flags, const char* arg1_name, const ARG1_TYPE& arg1_val) { const int num_args = 1; @@ -1528,13 +1547,13 @@ num_args, &arg1_name, arg_types, arg_values, NULL, flags); } -template<class ARG1_TYPE> +template <class ARG1_TYPE> static inline base::trace_event::TraceEventHandle AddTraceEvent( char phase, const unsigned char* category_group_enabled, const char* name, unsigned long long id, - unsigned char flags, + unsigned int flags, const char* arg1_name, const ARG1_TYPE& arg1_val) { int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); @@ -1544,7 +1563,7 @@ arg1_name, arg1_val); } -template<class ARG1_TYPE, class ARG2_TYPE> +template <class ARG1_TYPE, class ARG2_TYPE> static inline base::trace_event::TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( char phase, @@ -1553,7 +1572,7 @@ unsigned long long id, int thread_id, const base::TraceTicks& timestamp, - unsigned char flags, + unsigned int flags, const char* arg1_name, const ARG1_TYPE& arg1_val, const char* arg2_name, @@ -1569,13 +1588,13 @@ num_args, arg_names, arg_types, arg_values, NULL, flags); } -template<class ARG1_TYPE, class ARG2_TYPE> +template <class ARG1_TYPE, class ARG2_TYPE> static inline base::trace_event::TraceEventHandle AddTraceEvent( char phase, const unsigned char* category_group_enabled, const char* name, unsigned long long id, - unsigned char flags, + unsigned int flags, const char* arg1_name, const ARG1_TYPE& arg1_val, const char* arg2_name,
diff --git a/trace_event/trace_event_android.cc b/trace_event/trace_event_android.cc index 465649d..4d25014 100644 --- a/trace_event/trace_event_android.cc +++ b/trace_event/trace_event_android.cc
@@ -12,6 +12,9 @@ #include "base/synchronization/waitable_event.h" #include "base/trace_event/trace_event.h" +namespace base { +namespace trace_event { + namespace { int g_atrace_fd = -1; @@ -24,28 +27,25 @@ unsigned long long id, const char** arg_names, const unsigned char* arg_types, - const base::trace_event::TraceEvent::TraceValue* arg_values, - const scoped_refptr<base::trace_event::ConvertableToTraceFormat>* - convertable_values, - unsigned char flags) { - std::string out = base::StringPrintf("%c|%d|%s", phase, getpid(), name); + const TraceEvent::TraceValue* arg_values, + const scoped_refptr<ConvertableToTraceFormat>* convertable_values, + unsigned int flags) { + std::string out = StringPrintf("%c|%d|%s", phase, getpid(), name); if (flags & TRACE_EVENT_FLAG_HAS_ID) - base::StringAppendF(&out, "-%" PRIx64, static_cast<uint64>(id)); + StringAppendF(&out, "-%" PRIx64, static_cast<uint64>(id)); out += '|'; - for (int i = 0; i < base::trace_event::kTraceMaxNumArgs && arg_names[i]; - ++i) { + for (int i = 0; i < kTraceMaxNumArgs && arg_names[i]; ++i) { if (i) out += ';'; out += arg_names[i]; out += '='; std::string::size_type value_start = out.length(); - if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) { + if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) convertable_values[i]->AppendAsTraceFormat(&out); - } else { - base::trace_event::TraceEvent::AppendValueAsJSON(arg_types[i], - arg_values[i], &out); - } + else + TraceEvent::AppendValueAsJSON(arg_types[i], arg_values[i], &out); + // Remove the quotes which may confuse the atrace script. ReplaceSubstringsAfterOffset(&out, value_start, "\\\"", "'"); ReplaceSubstringsAfterOffset(&out, value_start, "\"", ""); @@ -59,25 +59,21 @@ write(g_atrace_fd, out.c_str(), out.size()); } -void NoOpOutputCallback(base::WaitableEvent* complete_event, - const scoped_refptr<base::RefCountedString>&, +void NoOpOutputCallback(WaitableEvent* complete_event, + const scoped_refptr<RefCountedString>&, bool has_more_events) { if (!has_more_events) complete_event->Signal(); } -void EndChromeTracing(base::trace_event::TraceLog* trace_log, - base::WaitableEvent* complete_event) { +void EndChromeTracing(TraceLog* trace_log, WaitableEvent* complete_event) { trace_log->SetDisabled(); // Delete the buffered trace events as they have been sent to atrace. - trace_log->Flush(base::Bind(&NoOpOutputCallback, complete_event)); + trace_log->Flush(Bind(&NoOpOutputCallback, complete_event)); } } // namespace -namespace base { -namespace trace_event { - // These functions support Android systrace.py when 'webview' category is // traced. With the new adb_profile_chrome, we may have two phases: // - before WebView is ready for combined tracing, we can use adb_profile_chrome
diff --git a/trace_event/trace_event_argument.cc b/trace_event/trace_event_argument.cc index e83aa73..2da6258 100644 --- a/trace_event/trace_event_argument.cc +++ b/trace_event/trace_event_argument.cc
@@ -5,112 +5,464 @@ #include "base/trace_event/trace_event_argument.h" #include "base/json/json_writer.h" +#include "base/trace_event/trace_event_memory_overhead.h" #include "base/values.h" namespace base { namespace trace_event { -TracedValue::TracedValue() : root_(new DictionaryValue()) { - stack_.push_back(root_.get()); +namespace { +const char kTypeStartDict = '{'; +const char kTypeEndDict = '}'; +const char kTypeStartArray = '['; +const char kTypeEndArray = ']'; +const char kTypeBool = 'b'; +const char kTypeInt = 'i'; +const char kTypeDouble = 'd'; +const char kTypeString = 's'; +const char kTypeCStr = '*'; + +#ifndef NDEBUG +const bool kStackTypeDict = false; +const bool kStackTypeArray = true; +#define DCHECK_CURRENT_CONTAINER_IS(x) DCHECK_EQ(x, nesting_stack_.back()) +#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) DCHECK_EQ(x, nesting_stack_.size()) +#define DEBUG_PUSH_CONTAINER(x) nesting_stack_.push_back(x) +#define DEBUG_POP_CONTAINER() nesting_stack_.pop_back() +#else +#define DCHECK_CURRENT_CONTAINER_IS(x) \ + do { \ + } while (0) +#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) \ + do { \ + } while (0) +#define DEBUG_PUSH_CONTAINER(x) \ + do { \ + } while (0) +#define DEBUG_POP_CONTAINER() \ + do { \ + } while (0) +#endif + +inline void WriteKeyNameAsRawPtr(Pickle& pickle, const char* ptr) { + pickle.WriteBytes(&kTypeCStr, 1); + pickle.WriteUInt64(static_cast<uint64>(reinterpret_cast<uintptr_t>(ptr))); +} + +inline void WriteKeyNameAsStdString(Pickle& pickle, const std::string& str) { + pickle.WriteBytes(&kTypeString, 1); + pickle.WriteString(str); +} + +std::string ReadKeyName(PickleIterator& pickle_iterator) { + const char* type = nullptr; + bool res = pickle_iterator.ReadBytes(&type, 1); + std::string key_name; + if (res && *type == kTypeCStr) { + uint64 ptr_value = 0; + res = pickle_iterator.ReadUInt64(&ptr_value); + key_name = reinterpret_cast<const char*>(static_cast<uintptr_t>(ptr_value)); + } else if (res && *type == kTypeString) { + res = pickle_iterator.ReadString(&key_name); + } + DCHECK(res); + return key_name; +} +} // namespace + +TracedValue::TracedValue() : TracedValue(0) {} + +TracedValue::TracedValue(size_t capacity) { + DEBUG_PUSH_CONTAINER(kStackTypeDict); + if (capacity) + pickle_.Reserve(capacity); } TracedValue::~TracedValue() { - DCHECK_EQ(1u, stack_.size()); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + DEBUG_POP_CONTAINER(); + DCHECK_CONTAINER_STACK_DEPTH_EQ(0u); } void TracedValue::SetInteger(const char* name, int value) { - GetCurrentDictionary()->SetInteger(name, value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeInt, 1); + pickle_.WriteInt(value); + WriteKeyNameAsRawPtr(pickle_, name); +} + +void TracedValue::SetIntegerWithCopiedName(const std::string& name, int value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeInt, 1); + pickle_.WriteInt(value); + WriteKeyNameAsStdString(pickle_, name); } void TracedValue::SetDouble(const char* name, double value) { - GetCurrentDictionary()->SetDouble(name, value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeDouble, 1); + pickle_.WriteDouble(value); + WriteKeyNameAsRawPtr(pickle_, name); +} + +void TracedValue::SetDoubleWithCopiedName(const std::string& name, + double value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeDouble, 1); + pickle_.WriteDouble(value); + WriteKeyNameAsStdString(pickle_, name); } void TracedValue::SetBoolean(const char* name, bool value) { - GetCurrentDictionary()->SetBoolean(name, value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeBool, 1); + pickle_.WriteBool(value); + WriteKeyNameAsRawPtr(pickle_, name); +} + +void TracedValue::SetBooleanWithCopiedName(const std::string& name, + bool value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeBool, 1); + pickle_.WriteBool(value); + WriteKeyNameAsStdString(pickle_, name); } void TracedValue::SetString(const char* name, const std::string& value) { - GetCurrentDictionary()->SetString(name, value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeString, 1); + pickle_.WriteString(value); + WriteKeyNameAsRawPtr(pickle_, name); } -void TracedValue::SetValue(const char* name, scoped_ptr<Value> value) { - GetCurrentDictionary()->Set(name, value.Pass()); +void TracedValue::SetStringWithCopiedName(const std::string& name, + const std::string& value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + pickle_.WriteBytes(&kTypeString, 1); + pickle_.WriteString(value); + WriteKeyNameAsStdString(pickle_, name); +} + +void TracedValue::SetValue(const char* name, const TracedValue& value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + BeginDictionary(name); + pickle_.WriteBytes(value.pickle_.payload(), + static_cast<int>(value.pickle_.payload_size())); + EndDictionary(); +} + +void TracedValue::SetValueWithCopiedName(const std::string& name, + const TracedValue& value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + BeginDictionaryWithCopiedName(name); + pickle_.WriteBytes(value.pickle_.payload(), + static_cast<int>(value.pickle_.payload_size())); + EndDictionary(); } void TracedValue::BeginDictionary(const char* name) { - DictionaryValue* dictionary = new DictionaryValue(); - GetCurrentDictionary()->Set(name, make_scoped_ptr(dictionary)); - stack_.push_back(dictionary); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + DEBUG_PUSH_CONTAINER(kStackTypeDict); + pickle_.WriteBytes(&kTypeStartDict, 1); + WriteKeyNameAsRawPtr(pickle_, name); +} + +void TracedValue::BeginDictionaryWithCopiedName(const std::string& name) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + DEBUG_PUSH_CONTAINER(kStackTypeDict); + pickle_.WriteBytes(&kTypeStartDict, 1); + WriteKeyNameAsStdString(pickle_, name); } void TracedValue::BeginArray(const char* name) { - ListValue* array = new ListValue(); - GetCurrentDictionary()->Set(name, make_scoped_ptr(array)); - stack_.push_back(array); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + DEBUG_PUSH_CONTAINER(kStackTypeArray); + pickle_.WriteBytes(&kTypeStartArray, 1); + WriteKeyNameAsRawPtr(pickle_, name); +} + +void TracedValue::BeginArrayWithCopiedName(const std::string& name) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + DEBUG_PUSH_CONTAINER(kStackTypeArray); + pickle_.WriteBytes(&kTypeStartArray, 1); + WriteKeyNameAsStdString(pickle_, name); } void TracedValue::EndDictionary() { - DCHECK_GT(stack_.size(), 1u); - DCHECK(GetCurrentDictionary()); - stack_.pop_back(); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + DEBUG_POP_CONTAINER(); + pickle_.WriteBytes(&kTypeEndDict, 1); } void TracedValue::AppendInteger(int value) { - GetCurrentArray()->AppendInteger(value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + pickle_.WriteBytes(&kTypeInt, 1); + pickle_.WriteInt(value); } void TracedValue::AppendDouble(double value) { - GetCurrentArray()->AppendDouble(value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + pickle_.WriteBytes(&kTypeDouble, 1); + pickle_.WriteDouble(value); } void TracedValue::AppendBoolean(bool value) { - GetCurrentArray()->AppendBoolean(value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + pickle_.WriteBytes(&kTypeBool, 1); + pickle_.WriteBool(value); } void TracedValue::AppendString(const std::string& value) { - GetCurrentArray()->AppendString(value); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + pickle_.WriteBytes(&kTypeString, 1); + pickle_.WriteString(value); } void TracedValue::BeginArray() { - ListValue* array = new ListValue(); - GetCurrentArray()->Append(array); - stack_.push_back(array); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + DEBUG_PUSH_CONTAINER(kStackTypeArray); + pickle_.WriteBytes(&kTypeStartArray, 1); } void TracedValue::BeginDictionary() { - DictionaryValue* dictionary = new DictionaryValue(); - GetCurrentArray()->Append(dictionary); - stack_.push_back(dictionary); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + DEBUG_PUSH_CONTAINER(kStackTypeDict); + pickle_.WriteBytes(&kTypeStartDict, 1); } void TracedValue::EndArray() { - DCHECK_GT(stack_.size(), 1u); - DCHECK(GetCurrentArray()); - stack_.pop_back(); + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + DEBUG_POP_CONTAINER(); + pickle_.WriteBytes(&kTypeEndArray, 1); } -DictionaryValue* TracedValue::GetCurrentDictionary() { - DCHECK(!stack_.empty()); - DictionaryValue* dictionary = NULL; - stack_.back()->GetAsDictionary(&dictionary); - DCHECK(dictionary); - return dictionary; +void TracedValue::SetValue(const char* name, scoped_ptr<base::Value> value) { + SetBaseValueWithCopiedName(name, *value); } -ListValue* TracedValue::GetCurrentArray() { - DCHECK(!stack_.empty()); - ListValue* list = NULL; - stack_.back()->GetAsList(&list); - DCHECK(list); - return list; +void TracedValue::SetBaseValueWithCopiedName(const std::string& name, + const base::Value& value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + switch (value.GetType()) { + case base::Value::TYPE_NULL: + case base::Value::TYPE_BINARY: + NOTREACHED(); + break; + + case base::Value::TYPE_BOOLEAN: { + bool bool_value; + value.GetAsBoolean(&bool_value); + SetBooleanWithCopiedName(name, bool_value); + } break; + + case base::Value::TYPE_INTEGER: { + int int_value; + value.GetAsInteger(&int_value); + SetIntegerWithCopiedName(name, int_value); + } break; + + case base::Value::TYPE_DOUBLE: { + double double_value; + value.GetAsDouble(&double_value); + SetDoubleWithCopiedName(name, double_value); + } break; + + case base::Value::TYPE_STRING: { + const StringValue* string_value; + value.GetAsString(&string_value); + SetStringWithCopiedName(name, string_value->GetString()); + } break; + + case base::Value::TYPE_DICTIONARY: { + const DictionaryValue* dict_value; + value.GetAsDictionary(&dict_value); + BeginDictionaryWithCopiedName(name); + for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd(); + it.Advance()) { + SetBaseValueWithCopiedName(it.key(), it.value()); + } + EndDictionary(); + } break; + + case base::Value::TYPE_LIST: { + const ListValue* list_value; + value.GetAsList(&list_value); + BeginArrayWithCopiedName(name); + for (base::Value* base_value : *list_value) + AppendBaseValue(*base_value); + EndArray(); + } break; + } +} + +void TracedValue::AppendBaseValue(const base::Value& value) { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); + switch (value.GetType()) { + case base::Value::TYPE_NULL: + case base::Value::TYPE_BINARY: + NOTREACHED(); + break; + + case base::Value::TYPE_BOOLEAN: { + bool bool_value; + value.GetAsBoolean(&bool_value); + AppendBoolean(bool_value); + } break; + + case base::Value::TYPE_INTEGER: { + int int_value; + value.GetAsInteger(&int_value); + AppendInteger(int_value); + } break; + + case base::Value::TYPE_DOUBLE: { + double double_value; + value.GetAsDouble(&double_value); + AppendDouble(double_value); + } break; + + case base::Value::TYPE_STRING: { + const StringValue* string_value; + value.GetAsString(&string_value); + AppendString(string_value->GetString()); + } break; + + case base::Value::TYPE_DICTIONARY: { + const DictionaryValue* dict_value; + value.GetAsDictionary(&dict_value); + BeginDictionary(); + for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd(); + it.Advance()) { + SetBaseValueWithCopiedName(it.key(), it.value()); + } + EndDictionary(); + } break; + + case base::Value::TYPE_LIST: { + const ListValue* list_value; + value.GetAsList(&list_value); + BeginArray(); + for (base::Value* base_value : *list_value) + AppendBaseValue(*base_value); + EndArray(); + } break; + } +} + +scoped_ptr<base::Value> TracedValue::ToBaseValue() const { + scoped_ptr<DictionaryValue> root(new DictionaryValue); + DictionaryValue* cur_dict = root.get(); + ListValue* cur_list = nullptr; + std::vector<Value*> stack; + PickleIterator it(pickle_); + const char* type; + + while (it.ReadBytes(&type, 1)) { + DCHECK((cur_dict && !cur_list) || (cur_list && !cur_dict)); + switch (*type) { + case kTypeStartDict: { + auto new_dict = new DictionaryValue(); + if (cur_dict) { + cur_dict->Set(ReadKeyName(it), make_scoped_ptr(new_dict)); + stack.push_back(cur_dict); + cur_dict = new_dict; + } else { + cur_list->Append(make_scoped_ptr(new_dict)); + stack.push_back(cur_list); + cur_list = nullptr; + cur_dict = new_dict; + } + } break; + + case kTypeEndArray: + case kTypeEndDict: { + if (stack.back()->GetAsDictionary(&cur_dict)) { + cur_list = nullptr; + } else if (stack.back()->GetAsList(&cur_list)) { + cur_dict = nullptr; + } + stack.pop_back(); + } break; + + case kTypeStartArray: { + auto new_list = new ListValue(); + if (cur_dict) { + cur_dict->Set(ReadKeyName(it), make_scoped_ptr(new_list)); + stack.push_back(cur_dict); + cur_dict = nullptr; + cur_list = new_list; + } else { + cur_list->Append(make_scoped_ptr(new_list)); + stack.push_back(cur_list); + cur_list = new_list; + } + } break; + + case kTypeBool: { + bool value; + CHECK(it.ReadBool(&value)); + if (cur_dict) { + cur_dict->SetBoolean(ReadKeyName(it), value); + } else { + cur_list->AppendBoolean(value); + } + } break; + + case kTypeInt: { + int value; + CHECK(it.ReadInt(&value)); + if (cur_dict) { + cur_dict->SetInteger(ReadKeyName(it), value); + } else { + cur_list->AppendInteger(value); + } + } break; + + case kTypeDouble: { + double value; + CHECK(it.ReadDouble(&value)); + if (cur_dict) { + cur_dict->SetDouble(ReadKeyName(it), value); + } else { + cur_list->AppendDouble(value); + } + } break; + + case kTypeString: { + std::string value; + CHECK(it.ReadString(&value)); + if (cur_dict) { + cur_dict->SetString(ReadKeyName(it), value); + } else { + cur_list->AppendString(value); + } + } break; + + default: + NOTREACHED(); + } + } + DCHECK(stack.empty()); + return root.Pass(); } void TracedValue::AppendAsTraceFormat(std::string* out) const { + DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); + DCHECK_CONTAINER_STACK_DEPTH_EQ(1u); + + // TODO(primiano): this could be smarter, skip the ToBaseValue encoding and + // produce the JSON on its own. This will require refactoring JSONWriter + // to decouple the base::Value traversal from the JSON writing bits std::string tmp; - JSONWriter::Write(*stack_.front(), &tmp); + JSONWriter::Write(*ToBaseValue(), &tmp); *out += tmp; - DCHECK_EQ(1u, stack_.size()) << tmp; +} + +void TracedValue::EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) { + overhead->Add("TracedValue", + pickle_.GetTotalAllocatedSize() /* allocated size */, + pickle_.size() /* resident size */); } } // namespace trace_event
diff --git a/trace_event/trace_event_argument.h b/trace_event/trace_event_argument.h index 56f7c61..aab58bc 100644 --- a/trace_event/trace_event_argument.h +++ b/trace_event/trace_event_argument.h
@@ -9,11 +9,11 @@ #include <vector> #include "base/memory/scoped_ptr.h" +#include "base/pickle.h" #include "base/trace_event/trace_event.h" namespace base { -class DictionaryValue; -class ListValue; + class Value; namespace trace_event { @@ -21,18 +21,31 @@ class BASE_EXPORT TracedValue : public ConvertableToTraceFormat { public: TracedValue(); + explicit TracedValue(size_t capacity); void EndDictionary(); void EndArray(); + // These methods assume that |name| is a long lived "quoted" string. void SetInteger(const char* name, int value); void SetDouble(const char* name, double value); void SetBoolean(const char* name, bool value); void SetString(const char* name, const std::string& value); - void SetValue(const char* name, scoped_ptr<Value> value); + void SetValue(const char* name, const TracedValue& value); void BeginDictionary(const char* name); void BeginArray(const char* name); + // These, instead, can be safely passed a temporary string. + void SetIntegerWithCopiedName(const std::string& name, int value); + void SetDoubleWithCopiedName(const std::string& name, double value); + void SetBooleanWithCopiedName(const std::string& name, bool value); + void SetStringWithCopiedName(const std::string& name, + const std::string& value); + void SetValueWithCopiedName(const std::string& name, + const TracedValue& value); + void BeginDictionaryWithCopiedName(const std::string& name); + void BeginArrayWithCopiedName(const std::string& name); + void AppendInteger(int); void AppendDouble(double); void AppendBoolean(bool); @@ -40,16 +53,33 @@ void BeginArray(); void BeginDictionary(); + // ConvertableToTraceFormat implementation. void AppendAsTraceFormat(std::string* out) const override; + void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead) override; + + // DEPRECATED: do not use, here only for legacy reasons. These methods causes + // a copy-and-translation of the base::Value into the equivalent TracedValue. + // TODO(primiano): migrate the (three) existing clients to the cheaper + // SetValue(TracedValue) API. crbug.com/495628. + void SetValue(const char* name, scoped_ptr<base::Value> value); + void SetBaseValueWithCopiedName(const std::string& name, + const base::Value& value); + void AppendBaseValue(const base::Value& value); + + // Public for tests only. + scoped_ptr<base::Value> ToBaseValue() const; + private: ~TracedValue() override; - DictionaryValue* GetCurrentDictionary(); - ListValue* GetCurrentArray(); + Pickle pickle_; - scoped_ptr<base::Value> root_; - std::vector<Value*> stack_; // Weak references. +#ifndef NDEBUG + // In debug builds checks the pairings of {Start,End}{Dictionary,Array} + std::vector<bool> nesting_stack_; +#endif + DISALLOW_COPY_AND_ASSIGN(TracedValue); };
diff --git a/trace_event/trace_event_argument_unittest.cc b/trace_event/trace_event_argument_unittest.cc index c59910e..cb1cf2e 100644 --- a/trace_event/trace_event_argument_unittest.cc +++ b/trace_event/trace_event_argument_unittest.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "base/trace_event/trace_event_argument.h" +#include "base/values.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { @@ -14,10 +15,11 @@ value->SetDouble("double", 0.0); value->SetBoolean("bool", true); value->SetString("string", "string"); - std::string json; + std::string json = "PREFIX"; value->AppendAsTraceFormat(&json); - EXPECT_EQ("{\"bool\":true,\"double\":0.0,\"int\":2014,\"string\":\"string\"}", - json); + EXPECT_EQ( + "PREFIX{\"bool\":true,\"double\":0.0,\"int\":2014,\"string\":\"string\"}", + json); } TEST(TraceEventArgumentTest, Hierarchy) { @@ -49,5 +51,96 @@ json); } +TEST(TraceEventArgumentTest, LongStrings) { + std::string kLongString = "supercalifragilisticexpialidocious"; + std::string kLongString2 = "0123456789012345678901234567890123456789"; + char kLongString3[4096]; + for (size_t i = 0; i < sizeof(kLongString3); ++i) + kLongString3[i] = 'a' + (i % 25); + kLongString3[sizeof(kLongString3) - 1] = '\0'; + + scoped_refptr<TracedValue> value = new TracedValue(); + value->SetString("a", "short"); + value->SetString("b", kLongString); + value->BeginArray("c"); + value->AppendString(kLongString2); + value->AppendString(""); + value->BeginDictionary(); + value->SetString("a", kLongString3); + value->EndDictionary(); + value->EndArray(); + + std::string json; + value->AppendAsTraceFormat(&json); + EXPECT_EQ("{\"a\":\"short\",\"b\":\"" + kLongString + "\",\"c\":[\"" + + kLongString2 + "\",\"\",{\"a\":\"" + kLongString3 + "\"}]}", + json); +} + +TEST(TraceEventArgumentTest, PassBaseValue) { + FundamentalValue int_value(42); + FundamentalValue bool_value(true); + FundamentalValue double_value(42.0f); + + auto dict_value = make_scoped_ptr(new DictionaryValue); + dict_value->SetBoolean("bool", true); + dict_value->SetInteger("int", 42); + dict_value->SetDouble("double", 42.0f); + dict_value->SetString("string", std::string("a") + "b"); + dict_value->SetString("string", std::string("a") + "b"); + + auto list_value = make_scoped_ptr(new ListValue); + list_value->AppendBoolean(false); + list_value->AppendInteger(1); + list_value->AppendString("in_list"); + list_value->Append(dict_value.Pass()); + + scoped_refptr<TracedValue> value = new TracedValue(); + value->BeginDictionary("outer_dict"); + value->SetValue("inner_list", list_value.Pass()); + value->EndDictionary(); + + dict_value.reset(); + list_value.reset(); + + std::string json; + value->AppendAsTraceFormat(&json); + EXPECT_EQ( + "{\"outer_dict\":{\"inner_list\":[false,1,\"in_list\",{\"bool\":true," + "\"double\":42.0,\"int\":42,\"string\":\"ab\"}]}}", + json); +} + +TEST(TraceEventArgumentTest, PassTracedValue) { + auto dict_value = make_scoped_refptr(new TracedValue); + dict_value->SetInteger("a", 1); + + auto nested_dict_value = make_scoped_refptr(new TracedValue); + nested_dict_value->SetInteger("b", 2); + nested_dict_value->BeginArray("c"); + nested_dict_value->AppendString("foo"); + nested_dict_value->EndArray(); + + dict_value->SetValue("e", *nested_dict_value); + + // Check the merged result. + std::string json; + dict_value->AppendAsTraceFormat(&json); + EXPECT_EQ("{\"a\":1,\"e\":{\"b\":2,\"c\":[\"foo\"]}}", json); + + // Check that the passed nestd dict was left unouthced. + json = ""; + nested_dict_value->AppendAsTraceFormat(&json); + EXPECT_EQ("{\"b\":2,\"c\":[\"foo\"]}", json); + + // And that it is still usable. + nested_dict_value->SetInteger("f", 3); + nested_dict_value->BeginDictionary("g"); + nested_dict_value->EndDictionary(); + json = ""; + nested_dict_value->AppendAsTraceFormat(&json); + EXPECT_EQ("{\"b\":2,\"c\":[\"foo\"],\"f\":3,\"g\":{}}", json); +} + } // namespace trace_event } // namespace base
diff --git a/trace_event/trace_event_etw_export_win.cc b/trace_event/trace_event_etw_export_win.cc index d199bf5..98e4553 100644 --- a/trace_event/trace_event_etw_export_win.cc +++ b/trace_event/trace_event_etw_export_win.cc
@@ -7,6 +7,7 @@ #include "base/command_line.h" #include "base/logging.h" #include "base/memory/singleton.h" +#include "base/strings/string_tokenizer.h" #include "base/strings/utf_string_conversions.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event_impl.h" @@ -40,6 +41,55 @@ tEventRegister EventRegisterProc = nullptr; tEventWrite EventWriteProc = nullptr; tEventUnregister EventUnregisterProc = nullptr; + +// |filtered_event_group_names| contains the event categories that can be +// exported individually. These categories can be enabled by passing the correct +// keyword when starting the trace. A keyword is a 64-bit flag and we attribute +// one bit per category. We can therefore enable a particular category by +// setting its corresponding bit in the keyword. For events that are not present +// in |filtered_event_group_names|, we have two bits that control their +// behaviour. When bit 61 is enabled, any event that is not disabled by default +// (ie. doesn't start with disabled-by-default-) will be exported. Likewise, +// when bit 62 is enabled, any event that is disabled by default will be +// exported. +// +// Note that bit 63 (MSB) must always be set, otherwise tracing will be disabled +// by ETW. Therefore, the keyword will always be greater than +// 0x8000000000000000. +// +// Examples of passing keywords to the provider using xperf: +// # This exports "benchmark" and "cc" events +// xperf -start chrome -on Chrome:0x8000000000000009 +// +// # This exports "gpu", "netlog" and all other events that are not disabled by +// # default +// xperf -start chrome -on Chrome:0xA0000000000000A0 +// +// More info about starting a trace and keyword can be obtained by using the +// help section of xperf (xperf -help start). Note that xperf documentation +// refers to keywords as flags and there are two ways to enable them, using +// group names or the hex representation. We only support the latter. Also, we +// ignore the level. +const char* const filtered_event_group_names[] = { + "benchmark", // 0x1 + "blink", // 0x2 + "browser", // 0x4 + "cc", // 0x8 + "evdev", // 0x10 + "gpu", // 0x20 + "input", // 0x40 + "netlog", // 0x80 + "renderer.scheduler", // 0x100 + "toplevel", // 0x200 + "v8", // 0x400 + "disabled-by-default-cc.debug", // 0x800 + "disabled-by-default-cc.debug.picture", // 0x1000 + "disabled-by-default-toplevel.flow"}; // 0x2000 +const char* other_events_group_name = "__OTHER_EVENTS"; // 0x2000000000000000 +const char* disabled_other_events_group_name = + "__DISABLED_OTHER_EVENTS"; // 0x4000000000000000 +uint64 other_events_keyword_bit = 1ULL << 61; +uint64 disabled_other_events_keyword_bit = 1ULL << 62; } // namespace // Redirector function for EventRegister. Called by macros in @@ -77,7 +127,8 @@ namespace base { namespace trace_event { -TraceEventETWExport::TraceEventETWExport() : ETWExportEnabled_(false) { +TraceEventETWExport::TraceEventETWExport() + : etw_export_enabled_(false), etw_match_any_keyword_(0ULL) { // Find Advapi32.dll. This should always succeed. HMODULE AdvapiDLL = ::LoadLibraryW(L"Advapi32.dll"); if (AdvapiDLL) { @@ -92,6 +143,8 @@ // Register the ETW provider. If registration fails then the event logging // calls will fail (on XP this call will do nothing). EventRegisterChrome(); + + UpdateEnabledCategories(); } } @@ -108,13 +161,18 @@ // static void TraceEventETWExport::EnableETWExport() { if (GetInstance()) - GetInstance()->ETWExportEnabled_ = true; + GetInstance()->etw_export_enabled_ = true; } // static void TraceEventETWExport::DisableETWExport() { if (GetInstance()) - GetInstance()->ETWExportEnabled_ = false; + GetInstance()->etw_export_enabled_ = false; +} + +// static +bool TraceEventETWExport::IsETWExportEnabled() { + return (GetInstance() && GetInstance()->etw_export_enabled_); } // static @@ -129,7 +187,7 @@ const unsigned long long* arg_values, const scoped_refptr<ConvertableToTraceFormat>* convertable_values) { // We bail early in case exporting is disabled or no consumer is listening. - if (!GetInstance() || !GetInstance()->ETWExportEnabled_ || + if (!GetInstance() || !GetInstance()->etw_export_enabled_ || !EventEnabledChromeEvent()) return; @@ -211,7 +269,7 @@ // *total* process CPU time when ETW tracing, and many of the strings // created exceed WPA's 4094 byte limit and are shown as: // "Unable to parse data". See crbug.com/488257 - //convertable_values[i]->AppendAsTraceFormat(arg_values_string + i); + // convertable_values[i]->AppendAsTraceFormat(arg_values_string + i); } else { TraceEvent::TraceValue trace_event; trace_event.as_uint = arg_values[i]; @@ -236,7 +294,7 @@ const char* arg_value_2, const char* arg_name_3, const char* arg_value_3) { - if (!GetInstance() || !GetInstance()->ETWExportEnabled_ || + if (!GetInstance() || !GetInstance()->etw_export_enabled_ || !EventEnabledChromeEvent()) return; @@ -244,8 +302,78 @@ arg_value_2, arg_name_3, arg_value_3); } -void TraceEventETWExport::Resurrect() { - StaticMemorySingletonTraits<TraceEventETWExport>::Resurrect(); +// static +bool TraceEventETWExport::IsCategoryGroupEnabled( + const char* category_group_name) { + DCHECK(category_group_name); + auto instance = GetInstance(); + if (instance == nullptr) + return false; + + if (!instance->IsETWExportEnabled()) + return false; + + CStringTokenizer category_group_tokens( + category_group_name, category_group_name + strlen(category_group_name), + ","); + while (category_group_tokens.GetNext()) { + std::string category_group_token = category_group_tokens.token(); + if (instance->IsCategoryEnabled(category_group_token.c_str())) { + return true; + } + } + return false; +} + +bool TraceEventETWExport::UpdateEnabledCategories() { + if (etw_match_any_keyword_ == CHROME_Context.MatchAnyKeyword) + return false; + + // If the keyword has changed, update each category. + // Chrome_Context.MatchAnyKeyword is set by UIforETW (or other ETW trace + // recording tools) using the ETW infrastructure. This value will be set in + // all Chrome processes that have registered their ETW provider. + etw_match_any_keyword_ = CHROME_Context.MatchAnyKeyword; + for (int i = 0; i < ARRAYSIZE(filtered_event_group_names); i++) { + if (etw_match_any_keyword_ & (1ULL << i)) { + categories_status_[filtered_event_group_names[i]] = true; + } else { + categories_status_[filtered_event_group_names[i]] = false; + } + } + + // Also update the two default categories. + if (etw_match_any_keyword_ & other_events_keyword_bit) { + categories_status_[other_events_group_name] = true; + } else { + categories_status_[other_events_group_name] = false; + } + if (etw_match_any_keyword_ & disabled_other_events_keyword_bit) { + categories_status_[disabled_other_events_group_name] = true; + } else { + categories_status_[disabled_other_events_group_name] = false; + } + + return true; +} + +bool TraceEventETWExport::IsCategoryEnabled(const char* category_name) const { + // Try to find the category and return its status if found + auto it = categories_status_.find(category_name); + if (it != categories_status_.end()) + return it->second; + + // Otherwise return the corresponding default status by first checking if the + // category is disabled by default. + if (StringPiece(category_name).starts_with("disabled-by-default")) { + DCHECK(categories_status_.find(disabled_other_events_group_name) != + categories_status_.end()); + return categories_status_.find(disabled_other_events_group_name)->second; + } else { + DCHECK(categories_status_.find(other_events_group_name) != + categories_status_.end()); + return categories_status_.find(other_events_group_name)->second; + } } } // namespace trace_event
diff --git a/trace_event/trace_event_etw_export_win.h b/trace_event/trace_event_etw_export_win.h index eefe820..9f73d78 100644 --- a/trace_event/trace_event_etw_export_win.h +++ b/trace_event/trace_event_etw_export_win.h
@@ -6,7 +6,10 @@ #ifndef BASE_TRACE_EVENT_TRACE_EVENT_ETW_EXPORT_WIN_H_ #define BASE_TRACE_EVENT_TRACE_EVENT_ETW_EXPORT_WIN_H_ +#include <map> + #include "base/base_export.h" +#include "base/strings/string_piece.h" #include "base/trace_event/trace_event_impl.h" // Fwd. @@ -29,9 +32,9 @@ static void EnableETWExport(); static void DisableETWExport(); - static bool isETWExportEnabled() { - return (GetInstance() && GetInstance()->ETWExportEnabled_); - } + // Returns true if ETW is enabled. For now, this is true if the command line + // flag is specified. + static bool IsETWExportEnabled(); // Exports an event to ETW. This is mainly used in // TraceLog::AddTraceEventWithThreadIdAndTimestamp to export internal events. @@ -50,7 +53,7 @@ // to ETW. Supports three arguments to be passed to ETW. // TODO(georgesak): Allow different providers. static void AddCustomEvent(const char* name, - char const* phase, + const char* phase, const char* arg_name_1, const char* arg_value_1, const char* arg_name_2, @@ -58,14 +61,30 @@ const char* arg_name_3, const char* arg_value_3); - void Resurrect(); + // Returns true if any category in the group is enabled. + static bool IsCategoryGroupEnabled(const char* category_group_name); private: - bool ETWExportEnabled_; // Ensure only the provider can construct us. friend struct StaticMemorySingletonTraits<TraceEventETWExport>; TraceEventETWExport(); + // Updates the list of enabled categories by consulting the ETW keyword. + // Returns true if there was a change, false otherwise. + bool UpdateEnabledCategories(); + + // Returns true if the category is enabled. + bool IsCategoryEnabled(const char* category_name) const; + + // True if ETW is enabled. Allows hiding the exporting behind a flag. + bool etw_export_enabled_; + + // Maps category names to their status (enabled/disabled). + std::map<base::StringPiece, bool> categories_status_; + + // Local copy of the ETW keyword. + uint64 etw_match_any_keyword_; + DISALLOW_COPY_AND_ASSIGN(TraceEventETWExport); };
diff --git a/trace_event/trace_event_impl.cc b/trace_event/trace_event_impl.cc index 3d58d87..96858aa 100644 --- a/trace_event/trace_event_impl.cc +++ b/trace_event/trace_event_impl.cc
@@ -33,6 +33,9 @@ #include "base/threading/thread_id_name_manager.h" #include "base/threading/worker_pool.h" #include "base/time/time.h" +#include "base/trace_event/memory_dump_manager.h" +#include "base/trace_event/memory_dump_provider.h" +#include "base/trace_event/process_memory_dump.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event_synthetic_delay.h" @@ -214,6 +217,18 @@ return cloned_buffer.Pass(); } + void EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) override { + overhead->Add("TraceBufferRingBuffer", sizeof(*this)); + for (size_t queue_index = queue_head_; queue_index != queue_tail_; + queue_index = NextQueueIndex(queue_index)) { + size_t chunk_index = recyclable_chunks_queue_[queue_index]; + if (chunk_index >= chunks_.size()) // Skip uninitialized chunks. + continue; + chunks_[chunk_index]->EstimateTraceMemoryOverhead(overhead); + } + } + private: class ClonedTraceBuffer : public TraceBuffer { public: @@ -242,6 +257,10 @@ NOTIMPLEMENTED(); return scoped_ptr<TraceBuffer>(); } + void EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) override { + NOTIMPLEMENTED(); + } size_t current_iteration_index_; ScopedVector<TraceBufferChunk> chunks_; @@ -350,6 +369,23 @@ return scoped_ptr<TraceBuffer>(); } + void EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) override { + const size_t chunks_ptr_vector_allocated_size = + sizeof(*this) + max_chunks_ * sizeof(decltype(chunks_)::value_type); + const size_t chunks_ptr_vector_resident_size = + sizeof(*this) + chunks_.size() * sizeof(decltype(chunks_)::value_type); + overhead->Add("TraceBufferVector", chunks_ptr_vector_allocated_size, + chunks_ptr_vector_resident_size); + for (size_t i = 0; i < chunks_.size(); ++i) { + TraceBufferChunk* chunk = chunks_[i]; + // Skip the in-flight (nullptr) chunks. They will be accounted by the + // per-thread-local dumpers, see ThreadLocalEventBuffer::OnMemoryDump. + if (chunk) + chunk->EstimateTraceMemoryOverhead(overhead); + } + } + private: size_t in_flight_chunk_count_; size_t current_iteration_index_; @@ -398,11 +434,16 @@ } // namespace +TraceBufferChunk::TraceBufferChunk(uint32 seq) : next_free_(0), seq_(seq) {} + +TraceBufferChunk::~TraceBufferChunk() {} + void TraceBufferChunk::Reset(uint32 new_seq) { for (size_t i = 0; i < next_free_; ++i) chunk_[i].Reset(); next_free_ = 0; seq_ = new_seq; + cached_overhead_estimate_when_full_.reset(); } TraceEvent* TraceBufferChunk::AddTraceEvent(size_t* event_index) { @@ -419,6 +460,31 @@ return cloned_chunk.Pass(); } +void TraceBufferChunk::EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) { + if (cached_overhead_estimate_when_full_) { + DCHECK(IsFull()); + overhead->Update(*cached_overhead_estimate_when_full_); + return; + } + + // Cache the memory overhead estimate only if the chunk is full. + TraceEventMemoryOverhead* estimate = overhead; + if (IsFull()) { + cached_overhead_estimate_when_full_.reset(new TraceEventMemoryOverhead); + estimate = cached_overhead_estimate_when_full_.get(); + } + + estimate->Add("TraceBufferChunk", sizeof(*this)); + for (size_t i = 0; i < next_free_; ++i) + chunk_[i].EstimateTraceMemoryOverhead(estimate); + + if (IsFull()) { + estimate->AddSelf(); + overhead->Update(*estimate); + } +} + // A helper class that allows the lock to be acquired in the middle of the scope // and unlocks at the end of scope if locked. class TraceLog::OptionalAutoLock { @@ -529,7 +595,7 @@ const unsigned char* arg_types, const unsigned long long* arg_values, const scoped_refptr<ConvertableToTraceFormat>* convertable_values, - unsigned char flags) { + unsigned int flags) { timestamp_ = timestamp; thread_timestamp_ = thread_timestamp; duration_ = TimeDelta::FromInternalValue(-1); @@ -610,6 +676,7 @@ parameter_copy_storage_ = NULL; for (int i = 0; i < kTraceMaxNumArgs; ++i) convertable_values_[i] = NULL; + cached_memory_overhead_estimate_.reset(); } void TraceEvent::UpdateDuration(const TraceTicks& now, @@ -619,6 +686,29 @@ thread_duration_ = thread_now - thread_timestamp_; } +void TraceEvent::EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) { + if (!cached_memory_overhead_estimate_) { + cached_memory_overhead_estimate_.reset(new TraceEventMemoryOverhead); + cached_memory_overhead_estimate_->Add("TraceEvent", sizeof(*this)); + // TODO(primiano): parameter_copy_storage_ is refcounted and, in theory, + // could be shared by several events and we might overcount. In practice + // this is unlikely but it's worth checking. + if (parameter_copy_storage_) { + cached_memory_overhead_estimate_->AddRefCountedString( + *parameter_copy_storage_.get()); + } + for (size_t i = 0; i < kTraceMaxNumArgs; ++i) { + if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) { + convertable_values_[i]->EstimateTraceMemoryOverhead( + cached_memory_overhead_estimate_.get()); + } + } + cached_memory_overhead_estimate_->AddSelf(); + } + overhead->Update(*cached_memory_overhead_estimate_); +} + // static void TraceEvent::AppendValueAsJSON(unsigned char type, TraceEvent::TraceValue value, @@ -697,34 +787,34 @@ DCHECK(!strchr(name_, '"')); StringAppendF(out, "{\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64 "," - "\"ph\":\"%c\",\"cat\":\"%s\",\"name\":\"%s\",\"args\":{", + "\"ph\":\"%c\",\"cat\":\"%s\",\"name\":\"%s\",\"args\":", process_id, thread_id_, time_int64, phase_, category_group_name, name_); // Output argument names and values, stop at first NULL argument name. - if (arg_names_[0]) { - bool allow_args = argument_filter_predicate.is_null() || - argument_filter_predicate.Run(category_group_name, name_); + bool strip_args = arg_names_[0] && !argument_filter_predicate.is_null() && + !argument_filter_predicate.Run(category_group_name, name_); - if (allow_args) { - for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) { - if (i > 0) - *out += ","; - *out += "\""; - *out += arg_names_[i]; - *out += "\":"; + if (strip_args) { + *out += "\"__stripped__\""; + } else { + *out += "{"; - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) - convertable_values_[i]->AppendAsTraceFormat(out); - else - AppendValueAsJSON(arg_types_[i], arg_values_[i], out); - } - } else { - *out += "\"stripped\":1"; + for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) { + if (i > 0) + *out += ","; + *out += "\""; + *out += arg_names_[i]; + *out += "\":"; + + if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) + convertable_values_[i]->AppendAsTraceFormat(out); + else + AppendValueAsJSON(arg_types_[i], arg_values_[i], out); } - } - *out += "}"; + *out += "}"; + } if (phase_ == TRACE_EVENT_PHASE_COMPLETE) { int64 duration = duration_.ToInternalValue(); @@ -753,6 +843,9 @@ if (flags_ & TRACE_EVENT_FLAG_HAS_ID) StringAppendF(out, ",\"id\":\"0x%" PRIx64 "\"", static_cast<uint64>(id_)); + if (flags_ & TRACE_EVENT_FLAG_BIND_TO_ENCLOSING) + StringAppendF(out, ",\"bp\":\"e\""); + // Instant events also output their scope. if (phase_ == TRACE_EVENT_PHASE_INSTANT) { char scope = '?'; @@ -984,7 +1077,8 @@ //////////////////////////////////////////////////////////////////////////////// class TraceLog::ThreadLocalEventBuffer - : public MessageLoop::DestructionObserver { + : public MessageLoop::DestructionObserver, + public MemoryDumpProvider { public: ThreadLocalEventBuffer(TraceLog* trace_log); ~ThreadLocalEventBuffer() override; @@ -1008,6 +1102,9 @@ // MessageLoop::DestructionObserver void WillDestroyCurrentMessageLoop() override; + // MemoryDumpProvider implementation. + bool OnMemoryDump(ProcessMemoryDump* pmd) override; + void FlushWhileLocked(); void CheckThisIsCurrentBuffer() const { @@ -1036,6 +1133,10 @@ MessageLoop* message_loop = MessageLoop::current(); message_loop->AddDestructionObserver(this); + // This is to report the local memory usage when memory-infra is enabled. + MemoryDumpManager::GetInstance()->RegisterDumpProvider( + this, ThreadTaskRunnerHandle::Get()); + AutoLock lock(trace_log->lock_); trace_log->thread_message_loops_.insert(message_loop); } @@ -1043,6 +1144,7 @@ TraceLog::ThreadLocalEventBuffer::~ThreadLocalEventBuffer() { CheckThisIsCurrentBuffer(); MessageLoop::current()->RemoveDestructionObserver(this); + MemoryDumpManager::GetInstance()->UnregisterDumpProvider(this); // Zero event_count_ happens in either of the following cases: // - no event generated for the thread; @@ -1119,6 +1221,17 @@ delete this; } +bool TraceLog::ThreadLocalEventBuffer::OnMemoryDump(ProcessMemoryDump* pmd) { + if (!chunk_) + return true; + std::string dump_base_name = StringPrintf( + "tracing/thread_%d", static_cast<int>(PlatformThread::CurrentId())); + TraceEventMemoryOverhead overhead; + chunk_->EstimateTraceMemoryOverhead(&overhead); + overhead.DumpInto(dump_base_name.c_str(), pmd); + return true; +} + void TraceLog::ThreadLocalEventBuffer::FlushWhileLocked() { if (!chunk_) return; @@ -1195,11 +1308,46 @@ #endif logged_events_.reset(CreateTraceBuffer()); + + MemoryDumpManager::GetInstance()->RegisterDumpProvider(this); } TraceLog::~TraceLog() { } +void TraceLog::InitializeThreadLocalEventBufferIfSupported() { + // A ThreadLocalEventBuffer needs the message loop + // - to know when the thread exits; + // - to handle the final flush. + // For a thread without a message loop or the message loop may be blocked, the + // trace events will be added into the main buffer directly. + if (thread_blocks_message_loop_.Get() || !MessageLoop::current()) + return; + auto thread_local_event_buffer = thread_local_event_buffer_.Get(); + if (thread_local_event_buffer && + !CheckGeneration(thread_local_event_buffer->generation())) { + delete thread_local_event_buffer; + thread_local_event_buffer = NULL; + } + if (!thread_local_event_buffer) { + thread_local_event_buffer = new ThreadLocalEventBuffer(this); + thread_local_event_buffer_.Set(thread_local_event_buffer); + } +} + +bool TraceLog::OnMemoryDump(ProcessMemoryDump* pmd) { + TraceEventMemoryOverhead overhead; + overhead.Add("TraceLog", sizeof(*this)); + { + AutoLock lock(lock_); + if (logged_events_) + logged_events_->EstimateTraceMemoryOverhead(&overhead); + } + overhead.AddSelf(); + overhead.DumpInto("tracing/main_trace_log", pmd); + return true; +} + const unsigned char* TraceLog::GetCategoryGroupEnabled( const char* category_group) { TraceLog* tracelog = GetInstance(); @@ -1239,8 +1387,10 @@ event_callback_trace_config_.IsCategoryGroupEnabled(category_group)) enabled_flag |= ENABLED_FOR_EVENT_CALLBACK; #if defined(OS_WIN) - if (base::trace_event::TraceEventETWExport::isETWExportEnabled()) + if (base::trace_event::TraceEventETWExport::IsCategoryGroupEnabled( + category_group)) { enabled_flag |= ENABLED_FOR_ETW_EXPORT; + } #endif g_category_group_enabled[category_index] = enabled_flag; @@ -1627,6 +1777,17 @@ // 4. If any thread hasn't finish its flush in time, finish the flush. void TraceLog::Flush(const TraceLog::OutputCallback& cb, bool use_worker_thread) { + FlushInternal(cb, use_worker_thread, false); +} + +void TraceLog::CancelTracing(const OutputCallback& cb) { + SetDisabled(); + FlushInternal(cb, false, true); +} + +void TraceLog::FlushInternal(const TraceLog::OutputCallback& cb, + bool use_worker_thread, + bool discard_events) { use_worker_thread_ = use_worker_thread; if (IsEnabled()) { // Can't flush when tracing is enabled because otherwise PostTask would @@ -1670,17 +1831,17 @@ if (thread_message_loop_task_runners.size()) { for (size_t i = 0; i < thread_message_loop_task_runners.size(); ++i) { thread_message_loop_task_runners[i]->PostTask( - FROM_HERE, - Bind(&TraceLog::FlushCurrentThread, Unretained(this), generation)); + FROM_HERE, Bind(&TraceLog::FlushCurrentThread, Unretained(this), + generation, discard_events)); } flush_task_runner_->PostDelayedTask( - FROM_HERE, - Bind(&TraceLog::OnFlushTimeout, Unretained(this), generation), + FROM_HERE, Bind(&TraceLog::OnFlushTimeout, Unretained(this), generation, + discard_events), TimeDelta::FromMilliseconds(kThreadFlushTimeoutMs)); return; } - FinishFlush(generation); + FinishFlush(generation, discard_events); } // Usually it runs on a different thread. @@ -1693,28 +1854,24 @@ // The callback need to be called at least once even if there is no events // to let the caller know the completion of flush. - bool has_more_events = true; - do { - scoped_refptr<RefCountedString> json_events_str_ptr = - new RefCountedString(); - - while (json_events_str_ptr->size() < kTraceEventBufferSizeInBytes) { - const TraceBufferChunk* chunk = logged_events->NextChunk(); - has_more_events = chunk != NULL; - if (!chunk) - break; - for (size_t j = 0; j < chunk->size(); ++j) { - if (json_events_str_ptr->size()) - json_events_str_ptr->data().append(",\n"); - chunk->GetEventAt(j)->AppendAsJSON(&(json_events_str_ptr->data()), - argument_filter_predicate); + scoped_refptr<RefCountedString> json_events_str_ptr = new RefCountedString(); + while (const TraceBufferChunk* chunk = logged_events->NextChunk()) { + for (size_t j = 0; j < chunk->size(); ++j) { + size_t size = json_events_str_ptr->size(); + if (size > kTraceEventBufferSizeInBytes) { + flush_output_callback.Run(json_events_str_ptr, true); + json_events_str_ptr = new RefCountedString(); + } else if (size) { + json_events_str_ptr->data().append(",\n"); } + chunk->GetEventAt(j)->AppendAsJSON(&(json_events_str_ptr->data()), + argument_filter_predicate); } - flush_output_callback.Run(json_events_str_ptr, has_more_events); - } while (has_more_events); + } + flush_output_callback.Run(json_events_str_ptr, false); } -void TraceLog::FinishFlush(int generation) { +void TraceLog::FinishFlush(int generation, bool discard_events) { scoped_ptr<TraceBuffer> previous_logged_events; OutputCallback flush_output_callback; TraceEvent::ArgumentFilterPredicate argument_filter_predicate; @@ -1739,6 +1896,14 @@ } } + if (discard_events) { + if (!flush_output_callback.is_null()) { + scoped_refptr<RefCountedString> empty_result = new RefCountedString; + flush_output_callback.Run(empty_result, false); + } + return; + } + if (use_worker_thread_ && WorkerPool::PostTask( FROM_HERE, Bind(&TraceLog::ConvertTraceEventsToTraceFormat, @@ -1754,7 +1919,7 @@ } // Run in each thread holding a local event buffer. -void TraceLog::FlushCurrentThread(int generation) { +void TraceLog::FlushCurrentThread(int generation, bool discard_events) { { AutoLock lock(lock_); if (!CheckGeneration(generation) || !flush_task_runner_) { @@ -1772,10 +1937,11 @@ return; flush_task_runner_->PostTask( - FROM_HERE, Bind(&TraceLog::FinishFlush, Unretained(this), generation)); + FROM_HERE, Bind(&TraceLog::FinishFlush, Unretained(this), generation, + discard_events)); } -void TraceLog::OnFlushTimeout(int generation) { +void TraceLog::OnFlushTimeout(int generation, bool discard_events) { { AutoLock lock(lock_); if (!CheckGeneration(generation) || !flush_task_runner_) { @@ -1794,7 +1960,7 @@ LOG(WARNING) << "Thread: " << (*it)->thread_name(); } } - FinishFlush(generation); + FinishFlush(generation, discard_events); } void TraceLog::FlushButLeaveBufferIntact( @@ -1839,7 +2005,7 @@ const unsigned char* arg_types, const unsigned long long* arg_values, const scoped_refptr<ConvertableToTraceFormat>* convertable_values, - unsigned char flags) { + unsigned int flags) { int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); base::TraceTicks now = base::TraceTicks::Now(); return AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, @@ -1861,7 +2027,7 @@ const unsigned char* arg_types, const unsigned long long* arg_values, const scoped_refptr<ConvertableToTraceFormat>* convertable_values, - unsigned char flags) { + unsigned int flags) { TraceEventHandle handle = { 0, 0, 0 }; if (!*category_group_enabled) return handle; @@ -1885,24 +2051,10 @@ OffsetNow() : offset_event_timestamp; ThreadTicks thread_now = ThreadNow(); - ThreadLocalEventBuffer* thread_local_event_buffer = NULL; - // A ThreadLocalEventBuffer needs the message loop - // - to know when the thread exits; - // - to handle the final flush. - // For a thread without a message loop or the message loop may be blocked, the - // trace events will be added into the main buffer directly. - if (!thread_blocks_message_loop_.Get() && MessageLoop::current()) { - thread_local_event_buffer = thread_local_event_buffer_.Get(); - if (thread_local_event_buffer && - !CheckGeneration(thread_local_event_buffer->generation())) { - delete thread_local_event_buffer; - thread_local_event_buffer = NULL; - } - if (!thread_local_event_buffer) { - thread_local_event_buffer = new ThreadLocalEventBuffer(this); - thread_local_event_buffer_.Set(thread_local_event_buffer); - } - } + // |thread_local_event_buffer_| can be null if the current thread doesn't have + // a message loop or the message loop is blocked. + InitializeThreadLocalEventBufferIfSupported(); + auto thread_local_event_buffer = thread_local_event_buffer_.Get(); // Check and update the current thread name only if the event is for the // current thread to avoid locks in most cases. @@ -1927,8 +2079,9 @@ } else { // This is a thread id that we've seen before, but potentially with a // new name. - std::vector<StringPiece> existing_names; - Tokenize(existing_name->second, ",", &existing_names); + std::vector<StringPiece> existing_names = base::SplitStringPiece( + existing_name->second, ",", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); bool found = std::find(existing_names.begin(), existing_names.end(), new_name) != existing_names.end(); @@ -2194,9 +2347,8 @@ labels.push_back(it->second); } InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), - current_thread_id, - "process_labels", "labels", - JoinString(labels, ',')); + current_thread_id, "process_labels", "labels", + base::JoinString(labels, ",")); } // Thread sort indices. @@ -2334,6 +2486,11 @@ } } +void ConvertableToTraceFormat::EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) { + overhead->Add("ConvertableToTraceFormat(Unknown)", sizeof(*this)); +} + } // namespace trace_event } // namespace base
diff --git a/trace_event/trace_event_impl.h b/trace_event/trace_event_impl.h index 7fdc5e3..a6e95f4 100644 --- a/trace_event/trace_event_impl.h +++ b/trace_event/trace_event_impl.h
@@ -24,7 +24,9 @@ #include "base/synchronization/lock.h" #include "base/threading/thread.h" #include "base/threading/thread_local.h" +#include "base/trace_event/memory_dump_provider.h" #include "base/trace_event/trace_config.h" +#include "base/trace_event/trace_event_memory_overhead.h" // Older style trace macros with explicit id and extra data // Only these macros result in publishing data to ETW as currently implemented. @@ -65,6 +67,8 @@ // appended. virtual void AppendAsTraceFormat(std::string* out) const = 0; + virtual void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); + std::string ToString() const { std::string result; AppendAsTraceFormat(&result); @@ -117,12 +121,14 @@ const unsigned char* arg_types, const unsigned long long* arg_values, const scoped_refptr<ConvertableToTraceFormat>* convertable_values, - unsigned char flags); + unsigned int flags); void Reset(); void UpdateDuration(const TraceTicks& now, const ThreadTicks& thread_now); + void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead*); + // Serialize event data to JSON typedef base::Callback<bool(const char* category_group_name, const char* event_name)> ArgumentFilterPredicate; @@ -142,7 +148,7 @@ TimeDelta duration() const { return duration_; } TimeDelta thread_duration() const { return thread_duration_; } unsigned long long id() const { return id_; } - unsigned char flags() const { return flags_; } + unsigned int flags() const { return flags_; } // Exposed for unittesting: @@ -168,6 +174,7 @@ TimeDelta thread_duration_; // id_ can be used to store phase-specific data. unsigned long long id_; + scoped_ptr<TraceEventMemoryOverhead> cached_memory_overhead_estimate_; TraceValue arg_values_[kTraceMaxNumArgs]; const char* arg_names_[kTraceMaxNumArgs]; scoped_refptr<ConvertableToTraceFormat> convertable_values_[kTraceMaxNumArgs]; @@ -176,7 +183,7 @@ scoped_refptr<base::RefCountedString> parameter_copy_storage_; int thread_id_; char phase_; - unsigned char flags_; + unsigned int flags_; unsigned char arg_types_[kTraceMaxNumArgs]; DISALLOW_COPY_AND_ASSIGN(TraceEvent); @@ -185,10 +192,8 @@ // TraceBufferChunk is the basic unit of TraceBuffer. class BASE_EXPORT TraceBufferChunk { public: - explicit TraceBufferChunk(uint32 seq) - : next_free_(0), - seq_(seq) { - } + explicit TraceBufferChunk(uint32 seq); + ~TraceBufferChunk(); void Reset(uint32 new_seq); TraceEvent* AddTraceEvent(size_t* event_index); @@ -209,10 +214,13 @@ scoped_ptr<TraceBufferChunk> Clone() const; + void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); + static const size_t kTraceBufferChunkSize = 64; private: size_t next_free_; + scoped_ptr<TraceEventMemoryOverhead> cached_overhead_estimate_when_full_; TraceEvent chunk_[kTraceBufferChunkSize]; uint32 seq_; }; @@ -235,6 +243,11 @@ virtual const TraceBufferChunk* NextChunk() = 0; virtual scoped_ptr<TraceBuffer> CloneForIteration() const = 0; + + // Computes an estimate of the size of the buffer, including all the retained + // objects. + virtual void EstimateTraceMemoryOverhead( + TraceEventMemoryOverhead* overhead) = 0; }; // TraceResultBuffer collects and converts trace fragments returned by TraceLog @@ -289,7 +302,7 @@ size_t event_count; }; -class BASE_EXPORT TraceLog { +class BASE_EXPORT TraceLog : public MemoryDumpProvider { public: enum Mode { DISABLED = 0, @@ -321,6 +334,10 @@ // Retrieves a copy (for thread-safety) of the current TraceConfig. TraceConfig GetCurrentTraceConfig() const; + // Initializes the thread-local event buffer, if not already initialized and + // if the current thread supports that (has a message loop). + void InitializeThreadLocalEventBufferIfSupported(); + // Enables normal tracing (recording trace events in the trace buffer). // See TraceConfig comments for details on how to control what categories // will be traced. If tracing has already been enabled, |category_filter| will @@ -350,6 +367,8 @@ // on-demand. class BASE_EXPORT EnabledStateObserver { public: + virtual ~EnabledStateObserver() = default; + // Called just after the tracing system becomes enabled, outside of the // |lock_|. TraceLog::IsEnabled() is true at this point. virtual void OnTraceLogEnabled() = 0; @@ -365,6 +384,10 @@ TraceLogStatus GetStatus() const; bool BufferIsFull() const; + // Computes an estimate of the size of the TraceLog including all the retained + // objects. + void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); + // Not using base::Callback because of its limited by 7 parameters. // Also, using primitive type allows directly passing callback from WebCore. // WARNING: It is possible for the previously set callback to be called @@ -383,7 +406,7 @@ const char* const arg_names[], const unsigned char arg_types[], const unsigned long long arg_values[], - unsigned char flags); + unsigned int flags); // Enable tracing for EventCallback. void SetEventCallbackEnabled(const TraceConfig& trace_config, @@ -407,6 +430,9 @@ void Flush(const OutputCallback& cb, bool use_worker_thread = false); void FlushButLeaveBufferIntact(const OutputCallback& flush_output_callback); + // Cancels tracing and discards collected data. + void CancelTracing(const OutputCallback& cb); + // Called by TRACE_EVENT* macros, don't call this directly. // The name parameter is a category group for example: // TRACE_EVENT0("renderer,webkit", "WebViewImpl::HandleInputEvent") @@ -427,7 +453,7 @@ const unsigned char* arg_types, const unsigned long long* arg_values, const scoped_refptr<ConvertableToTraceFormat>* convertable_values, - unsigned char flags); + unsigned int flags); TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, @@ -440,7 +466,7 @@ const unsigned char* arg_types, const unsigned long long* arg_values, const scoped_refptr<ConvertableToTraceFormat>* convertable_values, - unsigned char flags); + unsigned int flags); static void AddTraceEventEtw(char phase, const char* category_group, const void* id, @@ -528,6 +554,9 @@ // by the Singleton class. friend struct DefaultSingletonTraits<TraceLog>; + // MemoryDumpProvider implementation. + bool OnMemoryDump(ProcessMemoryDump* pmd) override; + // Enable/disable each category group based on the current mode_, // category_filter_, event_callback_ and event_callback_category_filter_. // Enable the category group in the enabled mode if category_filter_ matches @@ -547,7 +576,7 @@ class OptionalAutoLock; TraceLog(); - ~TraceLog(); + ~TraceLog() override; const unsigned char* GetCategoryGroupEnabledInternal(const char* name); void AddMetadataEventsWhileLocked(); @@ -572,16 +601,20 @@ TraceEvent* GetEventByHandleInternal(TraceEventHandle handle, OptionalAutoLock* lock); + void FlushInternal(const OutputCallback& cb, + bool use_worker_thread, + bool discard_events); + // |generation| is used in the following callbacks to check if the callback // is called for the flush of the current |logged_events_|. - void FlushCurrentThread(int generation); + void FlushCurrentThread(int generation, bool discard_events); // Usually it runs on a different thread. static void ConvertTraceEventsToTraceFormat( scoped_ptr<TraceBuffer> logged_events, const TraceLog::OutputCallback& flush_output_callback, const TraceEvent::ArgumentFilterPredicate& argument_filter_predicate); - void FinishFlush(int generation); - void OnFlushTimeout(int generation); + void FinishFlush(int generation, bool discard_events); + void OnFlushTimeout(int generation, bool discard_events); int generation() const { return static_cast<int>(subtle::NoBarrier_Load(&generation_));
diff --git a/trace_event/trace_event_memory.cc b/trace_event/trace_event_memory.cc index 8959589..73c8536 100644 --- a/trace_event/trace_event_memory.cc +++ b/trace_event/trace_event_memory.cc
@@ -11,6 +11,7 @@ #include "base/memory/scoped_ptr.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/threading/thread_local_storage.h" #include "base/trace_event/trace_event.h" @@ -318,9 +319,9 @@ input_string.assign(input); } - std::vector<std::string> lines; - size_t line_count = Tokenize(input_string, "\n", &lines); - if (line_count == 0) { + std::vector<std::string> lines = base::SplitString( + input_string, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (lines.empty()) { DLOG(WARNING) << "No lines found"; return; } @@ -330,10 +331,8 @@ AppendHeapProfileTotalsAsTraceFormat(lines[0], output); // Handle the following stack trace lines. - for (size_t i = 1; i < line_count; ++i) { - const std::string& line = lines[i]; - AppendHeapProfileLineAsTraceFormat(line, output); - } + for (size_t i = 1; i < lines.size(); i++) + AppendHeapProfileLineAsTraceFormat(lines[i], output); output->append("]\n"); } @@ -348,8 +347,8 @@ // 55227 = Outstanding bytes (malloc bytes - free bytes) // 14653 = Total allocations (mallocs) // 2624014 = Total bytes (malloc bytes) - std::vector<std::string> tokens; - Tokenize(line, " :[]@", &tokens); + std::vector<std::string> tokens = base::SplitString( + line, " :[]@", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (tokens.size() < 4) { DLOG(WARNING) << "Invalid totals line " << line; return; @@ -378,8 +377,8 @@ // 0x7fa7fa9b9ba0 0x7fa7f4b3be13 = Stack trace represented as pointers to // static strings from trace event categories // and names. - std::vector<std::string> tokens; - Tokenize(line, " :[]@", &tokens); + std::vector<std::string> tokens = base::SplitString( + line, " :[]@", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); // It's valid to have no stack addresses, so only require 4 tokens. if (tokens.size() < 4) { DLOG(WARNING) << "Invalid line " << line;
diff --git a/trace_event/trace_event_memory.h b/trace_event/trace_event_memory.h index e2b3ae9..5b63db0 100644 --- a/trace_event/trace_event_memory.h +++ b/trace_event/trace_event_memory.h
@@ -43,7 +43,7 @@ HeapProfilerStartFunction heap_profiler_start_function, HeapProfilerStopFunction heap_profiler_stop_function, GetHeapProfileFunction get_heap_profile_function); - virtual ~TraceMemoryController(); + ~TraceMemoryController() override; // base::trace_event::TraceLog::EnabledStateChangedObserver overrides: void OnTraceLogEnabled() override;
diff --git a/trace_event/trace_event_memory_overhead.cc b/trace_event/trace_event_memory_overhead.cc new file mode 100644 index 0000000..9aea5c1 --- /dev/null +++ b/trace_event/trace_event_memory_overhead.cc
@@ -0,0 +1,151 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/trace_event/trace_event_memory_overhead.h" + +#include <algorithm> + +#include "base/memory/ref_counted_memory.h" +#include "base/strings/stringprintf.h" +#include "base/trace_event/memory_allocator_dump.h" +#include "base/trace_event/process_memory_dump.h" +#include "base/values.h" + +namespace { +size_t RoundUp(size_t size, size_t alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} +} // namespace + +namespace base { +namespace trace_event { + +TraceEventMemoryOverhead::TraceEventMemoryOverhead() {} + +TraceEventMemoryOverhead::~TraceEventMemoryOverhead() {} + +void TraceEventMemoryOverhead::AddOrCreateInternal( + const char* object_type, + size_t count, + size_t allocated_size_in_bytes, + size_t resident_size_in_bytes) { + auto it = allocated_objects_.find(object_type); + if (it == allocated_objects_.end()) { + allocated_objects_.insert(std::make_pair( + object_type, ObjectCountAndSize({count, allocated_size_in_bytes, + resident_size_in_bytes}))); + return; + } + it->second.count += count; + it->second.allocated_size_in_bytes += allocated_size_in_bytes; + it->second.resident_size_in_bytes += resident_size_in_bytes; +} + +void TraceEventMemoryOverhead::Add(const char* object_type, + size_t allocated_size_in_bytes) { + Add(object_type, allocated_size_in_bytes, allocated_size_in_bytes); +} + +void TraceEventMemoryOverhead::Add(const char* object_type, + size_t allocated_size_in_bytes, + size_t resident_size_in_bytes) { + AddOrCreateInternal(object_type, 1, allocated_size_in_bytes, + resident_size_in_bytes); +} + +void TraceEventMemoryOverhead::AddString(const std::string& str) { + // The number below are empirical and mainly based on profiling of real-world + // std::string implementations: + // - even short string end up malloc()-inc at least 32 bytes. + // - longer stings seem to malloc() multiples of 16 bytes. + Add("std::string", + sizeof(std::string) + std::max<size_t>(RoundUp(str.capacity(), 16), 32u)); +} + +void TraceEventMemoryOverhead::AddRefCountedString( + const RefCountedString& str) { + Add("RefCountedString", sizeof(RefCountedString)); + AddString(str.data()); +} + +void TraceEventMemoryOverhead::AddValue(const Value& value) { + switch (value.GetType()) { + case Value::TYPE_NULL: + case Value::TYPE_BOOLEAN: + case Value::TYPE_INTEGER: + case Value::TYPE_DOUBLE: + Add("FundamentalValue", sizeof(Value)); + break; + + case Value::TYPE_STRING: { + const StringValue* string_value = nullptr; + value.GetAsString(&string_value); + Add("StringValue", sizeof(StringValue)); + AddString(string_value->GetString()); + } break; + + case Value::TYPE_BINARY: { + const BinaryValue* binary_value = nullptr; + value.GetAsBinary(&binary_value); + Add("BinaryValue", sizeof(BinaryValue) + binary_value->GetSize()); + } break; + + case Value::TYPE_DICTIONARY: { + const DictionaryValue* dictionary_value = nullptr; + value.GetAsDictionary(&dictionary_value); + Add("DictionaryValue", sizeof(DictionaryValue)); + for (DictionaryValue::Iterator it(*dictionary_value); !it.IsAtEnd(); + it.Advance()) { + AddString(it.key()); + AddValue(it.value()); + } + } break; + + case Value::TYPE_LIST: { + const ListValue* list_value = nullptr; + value.GetAsList(&list_value); + Add("ListValue", sizeof(ListValue)); + for (const Value* v : *list_value) + AddValue(*v); + } break; + + default: + NOTREACHED(); + } +} + +void TraceEventMemoryOverhead::AddSelf() { + size_t estimated_size = sizeof(*this); + // If the SmallMap did overflow its static capacity, its elements will be + // allocated on the heap and have to be accounted separately. + if (allocated_objects_.UsingFullMap()) + estimated_size += sizeof(map_type::value_type) * allocated_objects_.size(); + Add("TraceEventMemoryOverhead", estimated_size); +} + +void TraceEventMemoryOverhead::Update(const TraceEventMemoryOverhead& other) { + for (const auto& it : other.allocated_objects_) { + AddOrCreateInternal(it.first, it.second.count, + it.second.allocated_size_in_bytes, + it.second.resident_size_in_bytes); + } +} + +void TraceEventMemoryOverhead::DumpInto(const char* base_name, + ProcessMemoryDump* pmd) const { + for (const auto& it : allocated_objects_) { + std::string dump_name = StringPrintf("%s/%s", base_name, it.first); + MemoryAllocatorDump* mad = pmd->CreateAllocatorDump(dump_name); + mad->AddScalar(MemoryAllocatorDump::kNameSize, + MemoryAllocatorDump::kUnitsBytes, + it.second.allocated_size_in_bytes); + mad->AddScalar("resident_size", MemoryAllocatorDump::kUnitsBytes, + it.second.resident_size_in_bytes); + mad->AddScalar(MemoryAllocatorDump::kNameObjectsCount, + MemoryAllocatorDump::kUnitsObjects, it.second.count); + } +} + +} // namespace trace_event +} // namespace base
diff --git a/trace_event/trace_event_memory_overhead.h b/trace_event/trace_event_memory_overhead.h new file mode 100644 index 0000000..8ecf12d --- /dev/null +++ b/trace_event/trace_event_memory_overhead.h
@@ -0,0 +1,71 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_ +#define BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_ + +#include "base/base_export.h" +#include "base/containers/hash_tables.h" +#include "base/containers/small_map.h" + +namespace base { + +class RefCountedString; +class Value; + +namespace trace_event { + +class ProcessMemoryDump; + +// Used to estimate the memory overhead of the tracing infrastructure. +class BASE_EXPORT TraceEventMemoryOverhead { + public: + TraceEventMemoryOverhead(); + ~TraceEventMemoryOverhead(); + + // Use this method to account the overhead of an object for which an estimate + // is known for both the allocated and resident memory. + void Add(const char* object_type, + size_t allocated_size_in_bytes, + size_t resident_size_in_bytes); + + // Similar to Add() above, but assumes that + // |resident_size_in_bytes| == |allocated_size_in_bytes|. + void Add(const char* object_type, size_t allocated_size_in_bytes); + + // Specialized profiling functions for commonly used object types. + void AddString(const std::string& str); + void AddValue(const Value& value); + void AddRefCountedString(const RefCountedString& str); + + // Call this after all the Add* methods above to account the memory used by + // this TraceEventMemoryOverhead instance itself. + void AddSelf(); + + // Adds up and merges all the values from |other| to this instance. + void Update(const TraceEventMemoryOverhead& other); + + void DumpInto(const char* base_name, ProcessMemoryDump* pmd) const; + + private: + struct ObjectCountAndSize { + size_t count; + size_t allocated_size_in_bytes; + size_t resident_size_in_bytes; + }; + using map_type = SmallMap<hash_map<const char*, ObjectCountAndSize>, 16>; + map_type allocated_objects_; + + void AddOrCreateInternal(const char* object_type, + size_t count, + size_t allocated_size_in_bytes, + size_t resident_size_in_bytes); + + DISALLOW_COPY_AND_ASSIGN(TraceEventMemoryOverhead); +}; + +} // namespace trace_event +} // namespace base + +#endif // BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_
diff --git a/trace_event/trace_event_system_stats_monitor.h b/trace_event/trace_event_system_stats_monitor.h index 051669a..7a63a14 100644 --- a/trace_event/trace_event_system_stats_monitor.h +++ b/trace_event/trace_event_system_stats_monitor.h
@@ -33,7 +33,7 @@ explicit TraceEventSystemStatsMonitor( scoped_refptr<SingleThreadTaskRunner> task_runner); - virtual ~TraceEventSystemStatsMonitor(); + ~TraceEventSystemStatsMonitor() override; // base::trace_event::TraceLog::EnabledStateChangedObserver overrides: void OnTraceLogEnabled() override;
diff --git a/trace_event/trace_event_unittest.cc b/trace_event/trace_event_unittest.cc index 2449d54..8248f28 100644 --- a/trace_event/trace_event_unittest.cc +++ b/trace_event/trace_event_unittest.cc
@@ -15,6 +15,7 @@ #include "base/memory/singleton.h" #include "base/process/process_handle.h" #include "base/single_thread_task_runner.h" +#include "base/strings/pattern.h" #include "base/strings/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "base/threading/platform_thread.h" @@ -47,6 +48,8 @@ const char kAsyncIdStr[] = "0x5"; const int kAsyncId2 = 6; const char kAsyncId2Str[] = "0x6"; +const int kFlowId = 7; +const char kFlowIdStr[] = "0x7"; const char kRecordAllCategoryFilter[] = "*"; @@ -85,7 +88,14 @@ TraceLog::RECORDING_MODE); } + void CancelTrace() { + WaitableEvent flush_complete_event(false, false); + CancelTraceAsync(&flush_complete_event); + flush_complete_event.Wait(); + } + void EndTraceAndFlush() { + num_flush_callbacks_ = 0; WaitableEvent flush_complete_event(false, false); EndTraceAndFlushAsync(&flush_complete_event); flush_complete_event.Wait(); @@ -103,6 +113,13 @@ flush_complete_event.Wait(); } + void CancelTraceAsync(WaitableEvent* flush_complete_event) { + TraceLog::GetInstance()->CancelTracing( + base::Bind(&TraceEventTestFixture::OnTraceDataCollected, + base::Unretained(static_cast<TraceEventTestFixture*>(this)), + base::Unretained(flush_complete_event))); + } + void EndTraceAndFlushAsync(WaitableEvent* flush_complete_event) { TraceLog::GetInstance()->SetDisabled(); TraceLog::GetInstance()->Flush( @@ -134,6 +151,7 @@ ASSERT_FALSE(tracelog->IsEnabled()); trace_buffer_.SetOutputCallback(json_output_.GetCallback()); event_watch_notification_ = 0; + num_flush_callbacks_ = 0; } void TearDown() override { if (TraceLog::GetInstance()) @@ -150,6 +168,7 @@ TraceResultBuffer trace_buffer_; TraceResultBuffer::SimpleOutput json_output_; int event_watch_notification_; + size_t num_flush_callbacks_; private: // We want our singleton torn down after each test. @@ -161,6 +180,10 @@ WaitableEvent* flush_complete_event, const scoped_refptr<base::RefCountedString>& events_str, bool has_more_events) { + num_flush_callbacks_++; + if (num_flush_callbacks_ > 1) { + EXPECT_FALSE(events_str->data().empty()); + } AutoLock lock(lock_); json_output_.json_output.clear(); trace_buffer_.Start(); @@ -430,6 +453,12 @@ "name1", "value1", "name2", "value2"); + TRACE_EVENT_FLOW_BEGIN0("all", "TRACE_EVENT_FLOW_BEGIN0 call", kFlowId); + TRACE_EVENT_FLOW_STEP0("all", "TRACE_EVENT_FLOW_STEP0 call", kFlowId, + "step1"); + TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0( + "all", "TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0 call", kFlowId); + TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW0 call", kAsyncId, NULL); TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW1 call", kAsyncId, "value"); TRACE_EVENT_END_ETW("TRACE_EVENT_END_ETW0 call", kAsyncId, NULL); @@ -613,6 +642,17 @@ EXPECT_SUB_FIND_("name2"); EXPECT_SUB_FIND_("value2"); + EXPECT_FIND_("TRACE_EVENT_FLOW_BEGIN0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kFlowIdStr); + EXPECT_FIND_("TRACE_EVENT_FLOW_STEP0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kFlowIdStr); + EXPECT_SUB_FIND_("step1"); + EXPECT_FIND_("TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kFlowIdStr); + EXPECT_FIND_("TRACE_EVENT_BEGIN_ETW0 call"); EXPECT_SUB_FIND_("id"); EXPECT_SUB_FIND_(kAsyncIdStr); @@ -882,6 +922,19 @@ ValidateAllTraceMacrosCreatedData(trace_parsed_); } +// Emit some events and validate that only empty strings are received +// if we tell Flush() to discard events. +TEST_F(TraceEventTestFixture, DataDiscarded) { + TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""), + TraceLog::RECORDING_MODE); + + TraceWithAllMacroVariants(NULL); + + CancelTrace(); + + EXPECT_TRUE(trace_parsed_.empty()); +} + class MockEnabledStateChangedObserver : public TraceLog::EnabledStateObserver { public: @@ -968,7 +1021,7 @@ : public TraceLog::EnabledStateObserver { public: AfterStateChangeEnabledStateObserver() {} - virtual ~AfterStateChangeEnabledStateObserver() {} + ~AfterStateChangeEnabledStateObserver() override {} // TraceLog::EnabledStateObserver overrides: void OnTraceLogEnabled() override { @@ -999,7 +1052,7 @@ : public TraceLog::EnabledStateObserver { public: SelfRemovingEnabledStateObserver() {} - virtual ~SelfRemovingEnabledStateObserver() {} + ~SelfRemovingEnabledStateObserver() override {} // TraceLog::EnabledStateObserver overrides: void OnTraceLogEnabled() override {} @@ -1052,6 +1105,43 @@ EndTraceAndFlush(); } +TEST_F(TraceEventTestFixture, TestTraceFlush) { + size_t min_traces = 1; + size_t max_traces = 1; + do { + max_traces *= 2; + TraceLog::GetInstance()->SetEnabled(TraceConfig(), + TraceLog::RECORDING_MODE); + for (size_t i = 0; i < max_traces; i++) { + TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD); + } + EndTraceAndFlush(); + } while (num_flush_callbacks_ < 2); + + while (min_traces + 50 < max_traces) { + size_t traces = (min_traces + max_traces) / 2; + TraceLog::GetInstance()->SetEnabled(TraceConfig(), + TraceLog::RECORDING_MODE); + for (size_t i = 0; i < traces; i++) { + TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD); + } + EndTraceAndFlush(); + if (num_flush_callbacks_ < 2) { + min_traces = traces - 10; + } else { + max_traces = traces + 10; + } + } + + for (size_t traces = min_traces; traces < max_traces; traces++) { + TraceLog::GetInstance()->SetEnabled(TraceConfig(), + TraceLog::RECORDING_MODE); + for (size_t i = 0; i < traces; i++) { + TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD); + } + EndTraceAndFlush(); + } +} // Test that categories work. TEST_F(TraceEventTestFixture, Categories) { @@ -2149,8 +2239,8 @@ bool IsTraceEventArgsWhitelisted(const char* category_group_name, const char* event_name) { - if (MatchPattern(category_group_name, "toplevel") && - MatchPattern(event_name, "*")) { + if (base::MatchPattern(category_group_name, "toplevel") && + base::MatchPattern(event_name, "*")) { return true; } @@ -2187,7 +2277,10 @@ dict->GetDictionary("args", &args_dict); ASSERT_TRUE(args_dict); EXPECT_FALSE(args_dict->GetInteger("int_two", &int_value)); - EXPECT_TRUE(args_dict->GetInteger("stripped", &int_value)); + + std::string args_string; + EXPECT_TRUE(dict->GetString("args", &args_string)); + EXPECT_EQ(args_string, "__stripped__"); } class TraceEventCallbackTest : public TraceEventTestFixture { @@ -2247,7 +2340,7 @@ const char* const arg_names[], const unsigned char arg_types[], const unsigned long long arg_values[], - unsigned char flags) { + unsigned int flags) { s_instance->collected_events_phases_.push_back(phase); s_instance->collected_events_categories_.push_back( TraceLog::GetCategoryGroupName(category_group_enabled));
diff --git a/trace_event/trace_event_win.cc b/trace_event/trace_event_win.cc index ebb55c8..fdbd35a 100644 --- a/trace_event/trace_event_win.cc +++ b/trace_event/trace_event_win.cc
@@ -16,16 +16,24 @@ // {3DADA31D-19EF-4dc1-B345-037927193422} const GUID kChromeTraceProviderName = { - 0x3dada31d, 0x19ef, 0x4dc1, 0xb3, 0x45, 0x3, 0x79, 0x27, 0x19, 0x34, 0x22 }; + 0x3dada31d, + 0x19ef, + 0x4dc1, + {0xb3, 0x45, 0x3, 0x79, 0x27, 0x19, 0x34, 0x22}}; // {B967AE67-BB22-49d7-9406-55D91EE1D560} const GUID kTraceEventClass32 = { - 0xb967ae67, 0xbb22, 0x49d7, 0x94, 0x6, 0x55, 0xd9, 0x1e, 0xe1, 0xd5, 0x60 }; + 0xb967ae67, + 0xbb22, + 0x49d7, + {0x94, 0x6, 0x55, 0xd9, 0x1e, 0xe1, 0xd5, 0x60}}; // {97BE602D-2930-4ac3-8046-B6763B631DFE} const GUID kTraceEventClass64 = { - 0x97be602d, 0x2930, 0x4ac3, 0x80, 0x46, 0xb6, 0x76, 0x3b, 0x63, 0x1d, 0xfe}; - + 0x97be602d, + 0x2930, + 0x4ac3, + {0x80, 0x46, 0xb6, 0x76, 0x3b, 0x63, 0x1d, 0xfe}}; TraceEventETWProvider::TraceEventETWProvider() : EtwTraceProvider(kChromeTraceProviderName) {
diff --git a/trace_event/winheap_dump_provider_win.cc b/trace_event/winheap_dump_provider_win.cc index 82bb016..d56d8d3 100644 --- a/trace_event/winheap_dump_provider_win.cc +++ b/trace_event/winheap_dump_provider_win.cc
@@ -6,6 +6,7 @@ #include <windows.h> +#include "base/debug/profiler.h" #include "base/trace_event/process_memory_dump.h" #include "base/win/windows_version.h" @@ -59,6 +60,13 @@ if (base::win::GetVersion() < base::win::VERSION_VISTA) return false; +// Disable this dump provider for the SyzyASan instrumented build +// because they don't support the heap walking functions yet. +#if defined(SYZYASAN) + if (base::debug::IsBinaryInstrumented()) + return false; +#endif + // Retrieves the number of heaps in the current process. DWORD number_of_heaps = ::GetProcessHeaps(0, NULL); WinHeapInfo all_heap_info = {0};
diff --git a/values.cc b/values.cc index 4534d27..5374d6c 100644 --- a/values.cc +++ b/values.cc
@@ -482,27 +482,29 @@ SetWithoutPathExpansion(path, new StringValue(in_value)); } -bool DictionaryValue::Get(const std::string& path, - const Value** out_value) const { +bool DictionaryValue::Get(StringPiece path, const Value** out_value) const { DCHECK(IsStringUTF8(path)); - std::string current_path(path); + StringPiece current_path(path); const DictionaryValue* current_dictionary = this; for (size_t delimiter_position = current_path.find('.'); delimiter_position != std::string::npos; delimiter_position = current_path.find('.')) { const DictionaryValue* child_dictionary = NULL; - if (!current_dictionary->GetDictionary( - current_path.substr(0, delimiter_position), &child_dictionary)) + if (!current_dictionary->GetDictionaryWithoutPathExpansion( + current_path.substr(0, delimiter_position).as_string(), + &child_dictionary)) { return false; + } current_dictionary = child_dictionary; - current_path.erase(0, delimiter_position + 1); + current_path = current_path.substr(delimiter_position + 1); } - return current_dictionary->GetWithoutPathExpansion(current_path, out_value); + return current_dictionary->GetWithoutPathExpansion(current_path.as_string(), + out_value); } -bool DictionaryValue::Get(const std::string& path, Value** out_value) { +bool DictionaryValue::Get(StringPiece path, Value** out_value) { return static_cast<const DictionaryValue&>(*this).Get( path, const_cast<const Value**>(out_value)); @@ -588,7 +590,7 @@ const_cast<const BinaryValue**>(out_value)); } -bool DictionaryValue::GetDictionary(const std::string& path, +bool DictionaryValue::GetDictionary(StringPiece path, const DictionaryValue** out_value) const { const Value* value; bool result = Get(path, &value); @@ -601,7 +603,7 @@ return true; } -bool DictionaryValue::GetDictionary(const std::string& path, +bool DictionaryValue::GetDictionary(StringPiece path, DictionaryValue** out_value) { return static_cast<const DictionaryValue&>(*this).GetDictionary( path,
diff --git a/values.h b/values.h index 7feef9d..0ff6217 100644 --- a/values.h +++ b/values.h
@@ -30,6 +30,7 @@ #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" +#include "base/strings/string_piece.h" namespace base { @@ -270,8 +271,8 @@ // Otherwise, it will return false and |out_value| will be untouched. // Note that the dictionary always owns the value that's returned. // |out_value| is optional and will only be set if non-NULL. - bool Get(const std::string& path, const Value** out_value) const; - bool Get(const std::string& path, Value** out_value); + bool Get(StringPiece path, const Value** out_value) const; + bool Get(StringPiece path, Value** out_value); // These are convenience forms of Get(). The value will be retrieved // and the return value will be true if the path is valid and the value at @@ -287,9 +288,8 @@ bool GetStringASCII(const std::string& path, std::string* out_value) const; bool GetBinary(const std::string& path, const BinaryValue** out_value) const; bool GetBinary(const std::string& path, BinaryValue** out_value); - bool GetDictionary(const std::string& path, - const DictionaryValue** out_value) const; - bool GetDictionary(const std::string& path, DictionaryValue** out_value); + bool GetDictionary(StringPiece path, const DictionaryValue** out_value) const; + bool GetDictionary(StringPiece path, DictionaryValue** out_value); bool GetList(const std::string& path, const ListValue** out_value) const; bool GetList(const std::string& path, ListValue** out_value);
diff --git a/version.cc b/version.cc index ede8a45..228dcb8 100644 --- a/version.cc +++ b/version.cc
@@ -24,15 +24,17 @@ // parsed successfully, false otherwise. bool ParseVersionNumbers(const std::string& version_str, std::vector<uint32_t>* parsed) { - std::vector<std::string> numbers; - SplitString(version_str, '.', &numbers); + std::vector<StringPiece> numbers = + SplitStringPiece(version_str, ".", KEEP_WHITESPACE, SPLIT_WANT_ALL); if (numbers.empty()) return false; - for (std::vector<std::string>::const_iterator it = numbers.begin(); - it != numbers.end(); ++it) { - if (StartsWithASCII(*it, "+", false)) + for (auto it = numbers.begin(); it != numbers.end(); ++it) { + if (StartsWith(*it, "+", CompareCase::SENSITIVE)) return false; + + // TODO(brettw) when we have a StringPiece version of StringToUint, delete + // this string conversion. unsigned int num; if (!StringToUint(*it, &num)) return false; @@ -98,8 +100,8 @@ // static bool Version::IsValidWildcardString(const std::string& wildcard_string) { std::string version_string = wildcard_string; - if (EndsWith(wildcard_string.c_str(), ".*", false)) - version_string = wildcard_string.substr(0, wildcard_string.size() - 2); + if (EndsWith(version_string, ".*", CompareCase::SENSITIVE)) + version_string.resize(version_string.size() - 2); Version version(version_string); return version.IsValid(); @@ -117,7 +119,7 @@ DCHECK(Version::IsValidWildcardString(wildcard_string)); // Default behavior if the string doesn't end with a wildcard. - if (!EndsWith(wildcard_string.c_str(), ".*", false)) { + if (!EndsWith(wildcard_string, ".*", CompareCase::SENSITIVE)) { Version version(wildcard_string); DCHECK(version.IsValid()); return CompareTo(version);
diff --git a/win/iat_patch_function.cc b/win/iat_patch_function.cc index 13acd65..2e6ed40 100644 --- a/win/iat_patch_function.cc +++ b/win/iat_patch_function.cc
@@ -219,10 +219,9 @@ IATPatchFunction::IATPatchFunction() : module_handle_(NULL), + intercept_function_(NULL), original_function_(NULL), - iat_thunk_(NULL), - intercept_function_(NULL) { -} + iat_thunk_(NULL) {} IATPatchFunction::~IATPatchFunction() { if (NULL != intercept_function_) {
diff --git a/win/scoped_comptr.h b/win/scoped_comptr.h index 373c0c3..ade12fe 100644 --- a/win/scoped_comptr.h +++ b/win/scoped_comptr.h
@@ -52,24 +52,24 @@ // Note that this function equates to IUnknown::Release and should not // be confused with e.g. scoped_ptr::release(). void Release() { - if (ptr_ != NULL) { - ptr_->Release(); - ptr_ = NULL; + if (this->ptr_ != NULL) { + this->ptr_->Release(); + this->ptr_ = NULL; } } // Sets the internal pointer to NULL and returns the held object without // releasing the reference. Interface* Detach() { - Interface* p = ptr_; - ptr_ = NULL; + Interface* p = this->ptr_; + this->ptr_ = NULL; return p; } // Accepts an interface pointer that has already been addref-ed. void Attach(Interface* p) { - DCHECK(!ptr_); - ptr_ = p; + DCHECK(!this->ptr_); + this->ptr_ = p; } // Retrieves the pointer address. @@ -77,8 +77,8 @@ // The function DCHECKs on the current value being NULL. // Usage: Foo(p.Receive()); Interface** Receive() { - DCHECK(!ptr_) << "Object leak. Pointer must be NULL"; - return &ptr_; + DCHECK(!this->ptr_) << "Object leak. Pointer must be NULL"; + return &this->ptr_; } // A convenience for whenever a void pointer is needed as an out argument. @@ -89,18 +89,18 @@ template <class Query> HRESULT QueryInterface(Query** p) { DCHECK(p != NULL); - DCHECK(ptr_ != NULL); + DCHECK(this->ptr_ != NULL); // IUnknown already has a template version of QueryInterface // so the iid parameter is implicit here. The only thing this // function adds are the DCHECKs. - return ptr_->QueryInterface(p); + return this->ptr_->QueryInterface(p); } // QI for times when the IID is not associated with the type. HRESULT QueryInterface(const IID& iid, void** obj) { DCHECK(obj != NULL); - DCHECK(ptr_ != NULL); - return ptr_->QueryInterface(iid, obj); + DCHECK(this->ptr_ != NULL); + return this->ptr_->QueryInterface(iid, obj); } // Queries |other| for the interface this object wraps and returns the @@ -113,18 +113,18 @@ // Convenience wrapper around CoCreateInstance HRESULT CreateInstance(const CLSID& clsid, IUnknown* outer = NULL, DWORD context = CLSCTX_ALL) { - DCHECK(!ptr_); + DCHECK(!this->ptr_); HRESULT hr = ::CoCreateInstance(clsid, outer, context, *interface_id, - reinterpret_cast<void**>(&ptr_)); + reinterpret_cast<void**>(&this->ptr_)); return hr; } // Checks if the identity of |other| and this object is the same. bool IsSameObject(IUnknown* other) { - if (!other && !ptr_) + if (!other && !this->ptr_) return true; - if (!other || !ptr_) + if (!other || !this->ptr_) return false; ScopedComPtr<IUnknown> my_identity; @@ -147,8 +147,8 @@ // by statically casting the ScopedComPtr instance to the wrapped interface // and then making the call... but generally that shouldn't be necessary. BlockIUnknownMethods* operator->() const { - DCHECK(ptr_ != NULL); - return reinterpret_cast<BlockIUnknownMethods*>(ptr_); + DCHECK(this->ptr_ != NULL); + return reinterpret_cast<BlockIUnknownMethods*>(this->ptr_); } // Pull in operator=() from the parent class.
diff --git a/win/scoped_comptr_unittest.cc b/win/scoped_comptr_unittest.cc index d38752d..23090b0 100644 --- a/win/scoped_comptr_unittest.cc +++ b/win/scoped_comptr_unittest.cc
@@ -25,8 +25,10 @@ }; extern const IID dummy_iid; -const IID dummy_iid = { 0x12345678u, 0x1234u, 0x5678u, 01, 23, 45, 67, 89, - 01, 23, 45 }; +const IID dummy_iid = {0x12345678u, + 0x1234u, + 0x5678u, + {01, 23, 45, 67, 89, 01, 23, 45}}; } // namespace
diff --git a/win/scoped_variant.cc b/win/scoped_variant.cc index f57ab93..2cf2657 100644 --- a/win/scoped_variant.cc +++ b/win/scoped_variant.cc
@@ -9,7 +9,7 @@ namespace win { // Global, const instance of an empty variant. -const VARIANT ScopedVariant::kEmptyVariant = { VT_EMPTY }; +const VARIANT ScopedVariant::kEmptyVariant = {{{VT_EMPTY}}}; ScopedVariant::~ScopedVariant() { COMPILE_ASSERT(sizeof(ScopedVariant) == sizeof(VARIANT), ScopedVariantSize); @@ -82,7 +82,7 @@ } VARIANT ScopedVariant::Copy() const { - VARIANT ret = { VT_EMPTY }; + VARIANT ret = {{{VT_EMPTY}}}; ::VariantCopy(&ret, &var_); return ret; }
diff --git a/win/shortcut.cc b/win/shortcut.cc index f8b2182..57f8e61 100644 --- a/win/shortcut.cc +++ b/win/shortcut.cc
@@ -5,13 +5,18 @@ #include "base/win/shortcut.h" #include <shellapi.h> +#include <shldisp.h> #include <shlobj.h> #include <propkey.h> #include "base/files/file_util.h" +#include "base/strings/string_util.h" #include "base/threading/thread_restrictions.h" +#include "base/win/scoped_bstr.h" #include "base/win/scoped_comptr.h" +#include "base/win/scoped_handle.h" #include "base/win/scoped_propvariant.h" +#include "base/win/scoped_variant.h" #include "base/win/win_util.h" #include "base/win/windows_version.h" @@ -20,6 +25,90 @@ namespace { +// String resource IDs in shell32.dll. +const uint32_t kPinToTaskbarID = 5386; +const uint32_t kUnpinFromTaskbarID = 5387; + +// Traits for a GenericScopedHandle that will free a module on closure. +struct ModuleTraits { + typedef HMODULE Handle; + static Handle NullHandle() { return nullptr; } + static bool IsHandleValid(Handle module) { return !!module; } + static bool CloseHandle(Handle module) { return !!::FreeLibrary(module); } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ModuleTraits); +}; + +// An object that will free a module when it goes out of scope. +using ScopedLibrary = GenericScopedHandle<ModuleTraits, DummyVerifierTraits>; + +// Returns the shell resource string identified by |resource_id|, or an empty +// string on error. +string16 LoadShellResourceString(uint32_t resource_id) { + ScopedLibrary shell32(::LoadLibrary(L"shell32.dll")); + if (!shell32.IsValid()) + return string16(); + + const wchar_t* resource_ptr = nullptr; + int length = ::LoadStringW(shell32.Get(), resource_id, + reinterpret_cast<wchar_t*>(&resource_ptr), 0); + if (!length || !resource_ptr) + return string16(); + return string16(resource_ptr, length); +} + +// Uses the shell to perform the verb identified by |resource_id| on |path|. +bool DoVerbOnFile(uint32_t resource_id, const FilePath& path) { + string16 verb_name(LoadShellResourceString(resource_id)); + if (verb_name.empty()) + return false; + + ScopedComPtr<IShellDispatch> shell_dispatch; + HRESULT hresult = + shell_dispatch.CreateInstance(CLSID_Shell, nullptr, CLSCTX_INPROC_SERVER); + if (FAILED(hresult) || !shell_dispatch.get()) + return false; + + ScopedComPtr<Folder> folder; + hresult = shell_dispatch->NameSpace( + ScopedVariant(path.DirName().value().c_str()), folder.Receive()); + if (FAILED(hresult) || !folder.get()) + return false; + + ScopedComPtr<FolderItem> item; + hresult = folder->ParseName(ScopedBstr(path.BaseName().value().c_str()), + item.Receive()); + if (FAILED(hresult) || !item.get()) + return false; + + ScopedComPtr<FolderItemVerbs> verbs; + hresult = item->Verbs(verbs.Receive()); + if (FAILED(hresult) || !verbs.get()) + return false; + + long verb_count = 0; + hresult = verbs->get_Count(&verb_count); + if (FAILED(hresult)) + return false; + + for (long i = 0; i < verb_count; ++i) { + ScopedComPtr<FolderItemVerb> verb; + hresult = verbs->Item(ScopedVariant(i, VT_I4), verb.Receive()); + if (FAILED(hresult) || !verb.get()) + continue; + ScopedBstr name; + hresult = verb->get_Name(name.Receive()); + if (FAILED(hresult)) + continue; + if (StringPiece16(name, name.Length()) == verb_name) { + hresult = verb->DoIt(); + return SUCCEEDED(hresult); + } + } + return false; +} + // Initializes |i_shell_link| and |i_persist_file| (releasing them first if they // are already initialized). // If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|. @@ -314,28 +403,24 @@ return true; } -bool TaskbarPinShortcutLink(const wchar_t* shortcut) { +bool TaskbarPinShortcutLink(const FilePath& shortcut) { base::ThreadRestrictions::AssertIOAllowed(); // "Pin to taskbar" is only supported after Win7. if (GetVersion() < VERSION_WIN7) return false; - intptr_t result = reinterpret_cast<intptr_t>( - ShellExecute(NULL, L"taskbarpin", shortcut, NULL, NULL, 0)); - return result > 32; + return DoVerbOnFile(kPinToTaskbarID, shortcut); } -bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { +bool TaskbarUnpinShortcutLink(const FilePath& shortcut) { base::ThreadRestrictions::AssertIOAllowed(); // "Unpin from taskbar" is only supported after Win7. if (GetVersion() < VERSION_WIN7) return false; - intptr_t result = reinterpret_cast<intptr_t>( - ShellExecute(NULL, L"taskbarunpin", shortcut, NULL, NULL, 0)); - return result > 32; + return DoVerbOnFile(kUnpinFromTaskbarID, shortcut); } } // namespace win
diff --git a/win/shortcut.h b/win/shortcut.h index 6f7d10c..6c85f01 100644 --- a/win/shortcut.h +++ b/win/shortcut.h
@@ -155,12 +155,12 @@ // Pins a shortcut to the Windows 7 taskbar. The shortcut file must already // exist and be a shortcut that points to an executable. The app id of the // shortcut is used to group windows and must be set correctly. -BASE_EXPORT bool TaskbarPinShortcutLink(const wchar_t* shortcut); +BASE_EXPORT bool TaskbarPinShortcutLink(const FilePath& shortcut); // Unpins a shortcut from the Windows 7 taskbar. The shortcut must exist and // already be pinned to the taskbar. The app id of the shortcut is used as the // identifier for the taskbar item to remove and must be set correctly. -BASE_EXPORT bool TaskbarUnpinShortcutLink(const wchar_t* shortcut); +BASE_EXPORT bool TaskbarUnpinShortcutLink(const FilePath& shortcut); } // namespace win } // namespace base
diff --git a/win/win_util.cc b/win/win_util.cc index c5b06c4..7b4f612 100644 --- a/win/win_util.cc +++ b/win/win_util.cc
@@ -19,11 +19,14 @@ #include <signal.h> #include <stdlib.h> +#include "base/base_switches.h" +#include "base/command_line.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "base/win/metro.h" #include "base/win/registry.h" @@ -32,13 +35,16 @@ #include "base/win/scoped_propvariant.h" #include "base/win/windows_version.h" +namespace base { +namespace win { + namespace { // Sets the value of |property_key| to |property_value| in |property_store|. bool SetPropVariantValueForPropertyStore( IPropertyStore* property_store, const PROPERTYKEY& property_key, - const base::win::ScopedPropVariant& property_value) { + const ScopedPropVariant& property_value) { DCHECK(property_store); HRESULT result = property_store->SetValue(property_key, property_value.get()); @@ -48,31 +54,56 @@ } void __cdecl ForceCrashOnSigAbort(int) { - *((int*)0) = 0x1337; + *((volatile int*)0) = 0x1337; } const wchar_t kWindows8OSKRegPath[] = L"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}" L"\\LocalServer32"; +} // namespace + // Returns true if a physical keyboard is detected on Windows 8 and up. // Uses the Setup APIs to enumerate the attached keyboards and returns true // if the keyboard count is 1 or more.. While this will work in most cases // it won't work if there are devices which expose keyboard interfaces which // are attached to the machine. -bool IsKeyboardPresentOnSlate() { +bool IsKeyboardPresentOnSlate(std::string* reason) { + bool result = false; + + if (GetVersion() < VERSION_WIN7) { + *reason = "Detection not supported"; + return false; + } + // This function is only supported for Windows 8 and up. - DCHECK(base::win::GetVersion() >= base::win::VERSION_WIN8); + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableUsbKeyboardDetect)) { + if (reason) + *reason = "Detection disabled"; + return false; + } // This function should be only invoked for machines with touch screens. if ((GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) != NID_INTEGRATED_TOUCH) { - return true; + if (reason) { + *reason += "NID_INTEGRATED_TOUCH\n"; + result = true; + } else { + return true; + } } // If the device is docked, the user is treating the device as a PC. - if (GetSystemMetrics(SM_SYSTEMDOCKED) != 0) - return true; + if (GetSystemMetrics(SM_SYSTEMDOCKED) != 0) { + if (reason) { + *reason += "SM_SYSTEMDOCKED\n"; + result = true; + } else { + return true; + } + } // To determine whether a keyboard is present on the device, we do the // following:- @@ -103,7 +134,13 @@ // If there is no auto rotation sensor or rotation is not supported in // the current configuration, then we can assume that this is a desktop // or a traditional laptop. - return true; + if (reason) { + *reason += (auto_rotation_state & AR_NOSENSOR) ? "AR_NOSENSOR\n" + : "AR_NOT_SUPPORTED\n"; + result = true; + } else { + return true; + } } } @@ -114,8 +151,15 @@ POWER_PLATFORM_ROLE role = PowerDeterminePlatformRole(); if (((role == PlatformRoleMobile) || (role == PlatformRoleSlate)) && - (GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0)) - return false; + (GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0)) { + if (reason) { + *reason += (role == PlatformRoleMobile) ? "PlatformRoleMobile\n" + : "PlatformRoleSlate\n"; + // Don't change result here if it's already true. + } else { + return false; + } + } const GUID KEYBOARD_CLASS_GUID = { 0x4D36E96B, 0xE325, 0x11CE, @@ -124,13 +168,15 @@ // Query for all the keyboard devices. HDEVINFO device_info = SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, NULL, NULL, DIGCF_PRESENT); - if (device_info == INVALID_HANDLE_VALUE) - return false; + if (device_info == INVALID_HANDLE_VALUE) { + if (reason) + *reason += "No keyboard info\n"; + return result; + } // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If // the count is more than 1 we assume that a keyboard is present. This is // under the assumption that there will always be one keyboard device. - int keyboard_count = 0; for (DWORD i = 0;; ++i) { SP_DEVINFO_DATA device_info_data = { 0 }; device_info_data.cbSize = sizeof(device_info_data); @@ -146,23 +192,24 @@ if (status == CR_SUCCESS) { // To reduce the scope of the hack we only look for ACPI and HID\\VID // prefixes in the keyboard device ids. - if (StartsWith(device_id, L"ACPI", false) || - StartsWith(device_id, L"HID\\VID", false)) { - keyboard_count++; + if (StartsWith(device_id, L"ACPI", CompareCase::INSENSITIVE_ASCII) || + StartsWith(device_id, L"HID\\VID", CompareCase::INSENSITIVE_ASCII)) { + if (reason) { + *reason += "device: "; + *reason += WideToUTF8(device_id); + *reason += '\n'; + } + // The heuristic we are using is to check the count of keyboards and + // return true if the API's report one or more keyboards. Please note + // that this will break for non keyboard devices which expose a + // keyboard PDO. + result = true; } } } - // The heuristic we are using is to check the count of keyboards and return - // true if the API's report one or more keyboards. Please note that this - // will break for non keyboard devices which expose a keyboard PDO. - return keyboard_count >= 1; + return result; } -} // namespace - -namespace base { -namespace win { - static bool g_crash_on_process_detach = false; void GetNonClientMetrics(NONCLIENTMETRICS_XP* metrics) { @@ -181,7 +228,7 @@ HANDLE token = NULL; if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) return false; - base::win::ScopedHandle token_scoped(token); + ScopedHandle token_scoped(token); DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; scoped_ptr<BYTE[]> user_bytes(new BYTE[size]); @@ -226,11 +273,11 @@ // This can be slow if Windows ends up going to disk. Should watch this key // for changes and only read it once, preferably on the file thread. // http://code.google.com/p/chromium/issues/detail?id=61644 - base::ThreadRestrictions::ScopedAllowIO allow_io; + ThreadRestrictions::ScopedAllowIO allow_io; - base::win::RegKey key(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", - KEY_READ); + RegKey key(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", + KEY_READ); DWORD uac_enabled; if (key.ReadValueDW(L"EnableLUA", &uac_enabled) != ERROR_SUCCESS) return true; @@ -284,20 +331,20 @@ bool AddCommandToAutoRun(HKEY root_key, const string16& name, const string16& command) { - base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); + RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); return (autorun_key.WriteValue(name.c_str(), command.c_str()) == ERROR_SUCCESS); } bool RemoveCommandFromAutoRun(HKEY root_key, const string16& name) { - base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); + RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); return (autorun_key.DeleteValue(name.c_str()) == ERROR_SUCCESS); } bool ReadCommandFromAutoRun(HKEY root_key, const string16& name, string16* command) { - base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_QUERY_VALUE); + RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_QUERY_VALUE); return (autorun_key.ReadValue(name.c_str(), command) == ERROR_SUCCESS); } @@ -326,8 +373,8 @@ if (GetSystemMetrics(SM_MAXIMUMTOUCHES) == 0) return false; - base::win::Version version = base::win::GetVersion(); - if (version == base::win::VERSION_XP) + Version version = GetVersion(); + if (version == VERSION_XP) return (GetSystemMetrics(SM_TABLETPC) != 0); // If the device is docked, the user is treating the device as a PC. @@ -339,7 +386,7 @@ POWER_PLATFORM_ROLE role = PowerDeterminePlatformRole(); bool mobile_power_profile = (role == PlatformRoleMobile); bool slate_power_profile = false; - if (version >= base::win::VERSION_WIN8) + if (version >= VERSION_WIN8) slate_power_profile = (role == PlatformRoleSlate); if (mobile_power_profile || slate_power_profile) @@ -349,14 +396,13 @@ } bool DisplayVirtualKeyboard() { - if (base::win::GetVersion() < base::win::VERSION_WIN8) + if (GetVersion() < VERSION_WIN8) return false; - if (IsKeyboardPresentOnSlate()) + if (IsKeyboardPresentOnSlate(nullptr)) return false; - static base::LazyInstance<string16>::Leaky osk_path = - LAZY_INSTANCE_INITIALIZER; + static LazyInstance<string16>::Leaky osk_path = LAZY_INSTANCE_INITIALIZER; if (osk_path.Get().empty()) { // We need to launch TabTip.exe from the location specified under the @@ -367,9 +413,8 @@ // We don't want to launch TabTip.exe from // c:\program files (x86)\common files\microsoft shared\ink. This path is // normally found on 64 bit Windows. - base::win::RegKey key(HKEY_LOCAL_MACHINE, - kWindows8OSKRegPath, - KEY_READ | KEY_WOW64_64KEY); + RegKey key(HKEY_LOCAL_MACHINE, kWindows8OSKRegPath, + KEY_READ | KEY_WOW64_64KEY); DWORD osk_path_length = 1024; if (key.ReadValue(NULL, WriteInto(&osk_path.Get(), osk_path_length), @@ -410,7 +455,7 @@ common_program_files_path = common_program_files_wow6432.get(); DCHECK(!common_program_files_path.empty()); } else { - base::win::ScopedCoMem<wchar_t> common_program_files; + ScopedCoMem<wchar_t> common_program_files; if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, NULL, &common_program_files))) { return false; @@ -432,7 +477,7 @@ } bool DismissVirtualKeyboard() { - if (base::win::GetVersion() < base::win::VERSION_WIN8) + if (GetVersion() < VERSION_WIN8) return false; // We dismiss the virtual keyboard by generating the ESC keystroke @@ -474,21 +519,21 @@ } bool MaybeHasSHA256Support() { - const base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + const OSInfo* os_info = OSInfo::GetInstance(); - if (os_info->version() == base::win::VERSION_PRE_XP) + if (os_info->version() == VERSION_PRE_XP) return false; // Too old to have it and this OS is not supported anyway. - if (os_info->version() == base::win::VERSION_XP) + if (os_info->version() == VERSION_XP) return os_info->service_pack().major >= 3; // Windows XP SP3 has it. // Assume it is missing in this case, although it may not be. This category // includes Windows XP x64, and Windows Server, where a hotfix could be // deployed. - if (os_info->version() == base::win::VERSION_SERVER_2003) + if (os_info->version() == VERSION_SERVER_2003) return false; - DCHECK(os_info->version() >= base::win::VERSION_VISTA); + DCHECK(os_info->version() >= VERSION_VISTA); return true; // New enough to have SHA-256 support. }
diff --git a/win/win_util.h b/win/win_util.h index 8513f62..9f42e44 100644 --- a/win/win_util.h +++ b/win/win_util.h
@@ -132,6 +132,11 @@ // insight into how users use Chrome. BASE_EXPORT bool IsTabletDevice(); +// A slate is a touch device that may have a keyboard attached. This function +// returns true if a keyboard is attached and optionally will set the reason +// parameter to the detection method that was used to detect the keyboard. +BASE_EXPORT bool IsKeyboardPresentOnSlate(std::string* reason); + // Get the size of a struct up to and including the specified member. // This is necessary to set compatible struct sizes for different versions // of certain Windows APIs (e.g. SystemParametersInfo).
diff --git a/win/windows_version.cc b/win/windows_version.cc index fc2def3..35cdbb3 100644 --- a/win/windows_version.cc +++ b/win/windows_version.cc
@@ -73,7 +73,7 @@ service_pack_.major = version_info.wServicePackMajor; service_pack_.minor = version_info.wServicePackMinor; - SYSTEM_INFO system_info = { 0 }; + SYSTEM_INFO system_info = {}; ::GetNativeSystemInfo(&system_info); switch (system_info.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_INTEL: architecture_ = X86_ARCHITECTURE; break;