summaryrefslogtreecommitdiff
path: root/base
diff options
context:
space:
mode:
authorPatrick Rohr <prohr@google.com>2023-05-30 14:46:30 -0700
committerPatrick Rohr <prohr@google.com>2023-05-30 14:46:30 -0700
commit6ebb9017cd1a36fa33f054df1886756ac6de6953 (patch)
tree497480d492f1db4ca4db6d63e9173903f5de1770 /base
parent97ffc279ed0a0b52e81b26e803a265c574db2e82 (diff)
parent6e619ff2daf1f025aed9c3b67a7492b4b858f981 (diff)
downloadcronet-6ebb9017cd1a36fa33f054df1886756ac6de6953.tar.gz
Merge branch 'upstream-import' into upstream-staging
Update Cronet to version 114.0.5735.53. Test: none Change-Id: I0383be13fece212e77e387cef8f4dcf886ed579d
Diffstat (limited to 'base')
-rw-r--r--base/BUILD.gn22
-rw-r--r--base/allocator/dispatcher/tls.cc18
-rw-r--r--base/allocator/partition_alloc_features.cc13
-rw-r--r--base/allocator/partition_alloc_support.cc14
-rw-r--r--base/allocator/partition_allocator/BUILD.gn1
-rw-r--r--base/allocator/partition_allocator/page_allocator_internals_posix.h14
-rw-r--r--base/allocator/partition_allocator/partition_alloc.gni22
-rw-r--r--base/allocator/partition_allocator/partition_alloc_hooks.cc2
-rw-r--r--base/allocator/partition_allocator/partition_root.h49
-rw-r--r--base/allocator/partition_allocator/pointers/raw_ptr.h20
-rw-r--r--base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc98
-rw-r--r--base/android/field_trial_list.cc29
-rw-r--r--base/android/java/src/org/chromium/base/BuildInfo.java5
-rw-r--r--base/android/java/src/org/chromium/base/ByteArrayGenerator.java35
-rw-r--r--base/android/java/src/org/chromium/base/ResettersForTesting.java120
-rw-r--r--base/android/java/src/org/chromium/base/ThreadUtils.java163
-rw-r--r--base/android/java/src/org/chromium/base/TraceEvent.java36
-rw-r--r--base/android/java/src/org/chromium/base/WrappedClassLoader.java15
-rw-r--r--base/android/java/src/org/chromium/base/task/PostTask.java25
-rw-r--r--base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java11
-rw-r--r--base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java12
-rw-r--r--base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java5
-rw-r--r--base/android/junit/src/org/chromium/base/ResettersForTestingTest.java145
-rw-r--r--base/android/proguard/chromium_code.flags8
-rw-r--r--base/android/proguard/remove_logging.flags15
-rw-r--r--base/check_op.h1
-rw-r--r--base/containers/span.h1
-rw-r--r--base/critical_closure.h4
-rw-r--r--base/critical_closure_internal_ios.mm10
-rw-r--r--base/debug/allocation_trace_perftest.cc211
-rw-r--r--base/debug/debug.gni2
-rw-r--r--base/debug/dump_without_crashing.h7
-rw-r--r--base/features.cc15
-rw-r--r--base/features.h4
-rw-r--r--base/files/file_path.cc24
-rw-r--r--base/files/file_path_unittest.cc7
-rw-r--r--base/files/file_util.h26
-rw-r--r--base/fuchsia/startup_context.cc34
-rw-r--r--base/fuchsia/startup_context.h7
-rw-r--r--base/functional/callback.h4
-rw-r--r--base/ios/scoped_critical_action.mm2
-rw-r--r--base/ios/sim_header_shims.h12
-rw-r--r--base/mac/bundle_locations.h22
-rw-r--r--base/mac/bundle_locations.mm8
-rw-r--r--base/mac/foundation_util.h81
-rw-r--r--base/mac/scoped_nsautorelease_pool.h5
-rw-r--r--base/mac/scoped_nsobject.h167
-rw-r--r--base/mac/scoped_nsobject_unittest_arc.mm176
-rw-r--r--base/mac/scoped_typeref.h10
-rw-r--r--base/memory/madv_free_discardable_memory_posix.cc15
-rw-r--r--base/memory/rust_cfg_win_unittest.cc3
-rw-r--r--base/message_loop/fd_watch_controller_posix_unittest.cc19
-rw-r--r--base/message_loop/message_pump_glib.cc405
-rw-r--r--base/message_loop/message_pump_glib.h42
-rw-r--r--base/message_loop/message_pump_glib_unittest.cc63
-rw-r--r--base/message_loop/message_pump_libevent.h5
-rw-r--r--base/message_loop/message_pump_unittest.cc55
-rw-r--r--base/message_loop/message_pump_win.h5
-rw-r--r--base/metrics/field_trial.cc112
-rw-r--r--base/metrics/field_trial.h76
-rw-r--r--base/metrics/field_trial_list_including_low_anonymity.cc31
-rw-r--r--base/metrics/field_trial_list_including_low_anonymity.h98
-rw-r--r--base/metrics/field_trial_unittest.cc103
-rw-r--r--base/metrics/histogram_threadsafe_unittest.cc6
-rw-r--r--base/metrics/persistent_memory_allocator.cc50
-rw-r--r--base/metrics/persistent_memory_allocator.h2
-rw-r--r--base/metrics/persistent_sample_map.cc8
-rw-r--r--base/metrics/sample_vector.cc11
-rw-r--r--base/nix/xdg_util.cc5
-rw-r--r--base/nix/xdg_util.h3
-rw-r--r--base/nix/xdg_util_unittest.cc12
-rw-r--r--base/process/process_metrics.cc2
-rw-r--r--base/process/process_metrics.h26
-rw-r--r--base/process/process_metrics_ios.cc41
-rw-r--r--base/process/process_metrics_posix.cc2
-rw-r--r--base/process/process_metrics_win.cc2
-rw-r--r--base/profiler/native_unwinder_android_unittest.cc5
-rw-r--r--base/profiler/stack_sampler.cc11
-rw-r--r--base/profiler/stack_sampler.h4
-rw-r--r--base/profiler/stack_sampler_unittest.cc6
-rw-r--r--base/strings/DEPS8
-rw-r--r--base/strings/abseil_string_conversions.cc30
-rw-r--r--base/strings/abseil_string_conversions.h37
-rw-r--r--base/strings/abseil_string_conversions_unittest.cc69
-rw-r--r--base/strings/escape.cc13
-rw-r--r--base/strings/string_piece.h15
-rw-r--r--base/strings/string_piece_unittest.cc16
-rw-r--r--base/strings/string_util.h90
-rw-r--r--base/strings/string_util_unittest.cc74
-rw-r--r--base/strings/to_string.h110
-rw-r--r--base/strings/to_string_test.cc106
-rw-r--r--base/system/sys_info.cc42
-rw-r--r--base/system/sys_info.h8
-rw-r--r--base/task/cancelable_task_tracker.cc111
-rw-r--r--base/task/cancelable_task_tracker.h13
-rw-r--r--base/task/common/task_annotator.cc8
-rw-r--r--base/task/sequence_manager/thread_controller.h2
-rw-r--r--base/template_util.h9
-rw-r--r--base/template_util_unittest.cc49
-rw-r--r--base/test/BUILD.gn30
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java2
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java32
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java37
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java36
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java17
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java18
-rw-r--r--base/test/test_support_ios.mm5
-rw-r--r--base/test/test_trace_processor.plist20
-rw-r--r--base/tracing/protos/chrome_track_event.proto18
-rw-r--r--base/types/strong_alias.h4
-rw-r--r--base/types/strong_alias_unittest.cc8
-rw-r--r--base/types/supports_ostream_operator.h28
-rw-r--r--base/types/supports_ostream_operator_test.cc68
-rw-r--r--base/values.cc12
-rw-r--r--base/values.h3
-rw-r--r--base/win/com_init_util.cc9
-rw-r--r--base/win/iat_patch_function.cc17
-rw-r--r--base/win/iat_patch_function.h9
-rw-r--r--base/win/registry.cc640
-rw-r--r--base/win/registry.h76
-rw-r--r--base/win/registry_unittest.cc232
-rw-r--r--base/win/scoped_co_mem.h5
-rw-r--r--base/win/scoped_safearray.h9
123 files changed, 3849 insertions, 1261 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 467f64fe8..b5f3e520b 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -480,6 +480,8 @@ component("base") {
"metrics/dummy_histogram.h",
"metrics/field_trial.cc",
"metrics/field_trial.h",
+ "metrics/field_trial_list_including_low_anonymity.cc",
+ "metrics/field_trial_list_including_low_anonymity.h",
"metrics/field_trial_param_associator.cc",
"metrics/field_trial_param_associator.h",
"metrics/field_trial_params.cc",
@@ -626,8 +628,6 @@ component("base") {
"sequence_token.h",
"state_transitions.h",
"stl_util.h",
- "strings/abseil_string_conversions.cc",
- "strings/abseil_string_conversions.h",
"strings/abseil_string_number_conversions.cc",
"strings/abseil_string_number_conversions.h",
"strings/escape.cc",
@@ -661,6 +661,7 @@ component("base") {
"strings/stringprintf.cc",
"strings/stringprintf.h",
"strings/sys_string_conversions.h",
+ "strings/to_string.h",
"strings/utf_offset_string_conversions.cc",
"strings/utf_offset_string_conversions.h",
"strings/utf_string_conversion_utils.cc",
@@ -934,6 +935,7 @@ component("base") {
"types/optional_util.h",
"types/pass_key.h",
"types/strong_alias.h",
+ "types/supports_ostream_operator.h",
"types/token_type.h",
"types/variant_util.h",
"unguessable_token.cc",
@@ -2157,6 +2159,7 @@ component("base") {
"ios/sim_header_shims.h",
"mac/mach_port_rendezvous.cc",
"mac/mach_port_rendezvous.h",
+ "process/kill_mac.cc",
"process/launch_mac.cc",
"process/memory_mac.mm",
"process/port_provider_mac.cc",
@@ -2164,6 +2167,7 @@ component("base") {
"process/process_handle_mac.cc",
"process/process_iterator_ios.mm",
"process/process_mac.cc",
+ "process/process_metrics_posix.cc",
"process/process_posix.cc",
"sync_socket_posix.cc",
"synchronization/waitable_event_watcher_mac.cc",
@@ -2779,6 +2783,10 @@ test("base_perftests") {
# build, only do stack trace perftest for unofficial build
sources += [ "debug/stack_trace_perftest.cc" ]
}
+
+ if (build_allocation_stack_trace_recorder) {
+ sources += [ "debug/allocation_trace_perftest.cc" ]
+ }
}
test("base_i18n_perftests") {
@@ -2952,6 +2960,7 @@ if (is_apple) {
sources = [
"ios/crb_protocol_observers_unittest.mm",
"mac/bind_objc_block_unittest_arc.mm",
+ "mac/scoped_nsobject_unittest_arc.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
@@ -3255,7 +3264,6 @@ test("base_unittests") {
"sequence_token_unittest.cc",
"state_transitions_unittest.cc",
"stl_util_unittest.cc",
- "strings/abseil_string_conversions_unittest.cc",
"strings/abseil_string_number_conversions_unittest.cc",
"strings/escape_unittest.cc",
"strings/no_trigraphs_unittest.cc",
@@ -3270,6 +3278,7 @@ test("base_unittests") {
"strings/stringize_macros_unittest.cc",
"strings/stringprintf_unittest.cc",
"strings/sys_string_conversions_unittest.cc",
+ "strings/to_string_test.cc",
"strings/utf_offset_string_conversions_unittest.cc",
"strings/utf_string_conversions_unittest.cc",
"substring_set_matcher/string_pattern_unittest.cc",
@@ -3403,6 +3412,7 @@ test("base_unittests") {
"types/optional_util_unittest.cc",
"types/pass_key_unittest.cc",
"types/strong_alias_unittest.cc",
+ "types/supports_ostream_operator_test.cc",
"types/token_type_unittest.cc",
"types/variant_util_unittest.cc",
"unguessable_token_unittest.cc",
@@ -4026,8 +4036,6 @@ if (is_win && toolchain_has_rust) {
"//testing/gtest",
]
data_deps = [ "//build/rust/tests/test_control_flow_guard" ]
- cfg_exe = rebase_path("${root_out_dir}/test_control_flow_guard.exe")
- defines = [ "RUST_CFG_WIN_UNITTEST_PATH_TO_RUST_EXE=\"${cfg_exe}\"" ]
sources = [ "memory/rust_cfg_win_unittest.cc" ]
}
}
@@ -4252,7 +4260,6 @@ if (is_android) {
"android/java/src/org/chromium/base/BaseFeatureList.java",
"android/java/src/org/chromium/base/BuildInfo.java",
"android/java/src/org/chromium/base/BundleUtils.java",
- "android/java/src/org/chromium/base/ByteArrayGenerator.java",
"android/java/src/org/chromium/base/Callback.java",
"android/java/src/org/chromium/base/CallbackController.java",
"android/java/src/org/chromium/base/CollectionUtil.java",
@@ -4290,6 +4297,7 @@ if (is_android) {
"android/java/src/org/chromium/base/Promise.java",
"android/java/src/org/chromium/base/RadioUtils.java",
"android/java/src/org/chromium/base/RequiredCallback.java",
+ "android/java/src/org/chromium/base/ResettersForTesting.java",
"android/java/src/org/chromium/base/StreamUtil.java",
"android/java/src/org/chromium/base/StrictModeContext.java",
"android/java/src/org/chromium/base/SysUtils.java",
@@ -4500,6 +4508,7 @@ if (is_android) {
"test/android/javatests/src/org/chromium/base/test/LoadNative.java",
"test/android/javatests/src/org/chromium/base/test/MockitoErrorHandler.java",
"test/android/javatests/src/org/chromium/base/test/ResetCachedFlagValuesTestHook.java",
+ "test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java",
"test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java",
"test/android/javatests/src/org/chromium/base/test/TestListInstrumentationRunListener.java",
"test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java",
@@ -4645,6 +4654,7 @@ if (is_android) {
"android/junit/src/org/chromium/base/PathUtilsTest.java",
"android/junit/src/org/chromium/base/PiiEliderTest.java",
"android/junit/src/org/chromium/base/PromiseTest.java",
+ "android/junit/src/org/chromium/base/ResettersForTestingTest.java",
"android/junit/src/org/chromium/base/TimeUtilsTest.java",
"android/junit/src/org/chromium/base/TraceEventTest.java",
"android/junit/src/org/chromium/base/UnownedUserDataHostTest.java",
diff --git a/base/allocator/dispatcher/tls.cc b/base/allocator/dispatcher/tls.cc
index c33ac1f8c..652481067 100644
--- a/base/allocator/dispatcher/tls.cc
+++ b/base/allocator/dispatcher/tls.cc
@@ -9,14 +9,30 @@
#include "base/check.h"
#include "base/dcheck_is_on.h"
#include "base/immediate_crash.h"
+#include "build/build_config.h"
#include <sys/mman.h>
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
+#include <sys/prctl.h>
+#endif
+
namespace base::allocator::dispatcher::internal {
void* MMapAllocator::AllocateMemory(size_t size_in_bytes) {
void* const mmap_res = mmap(nullptr, size_in_bytes, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
+#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
+ if (mmap_res != MAP_FAILED) {
+ // Allow the anonymous memory region allocated by mmap(MAP_ANONYMOUS) to
+ // be identified in /proc/$PID/smaps. This helps improve visibility into
+ // Chromium's memory usage on Android.
+ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, mmap_res, size_in_bytes,
+ "tls-mmap-allocator");
+ }
+#endif
+#endif
return (mmap_res != MAP_FAILED) ? mmap_res : nullptr;
}
@@ -81,4 +97,4 @@ bool PThreadTLSSystem::SetThreadSpecificData(void* data) {
} // namespace base::allocator::dispatcher::internal
-#endif // USE_LOCAL_TLS_EMULATION() \ No newline at end of file
+#endif // USE_LOCAL_TLS_EMULATION()
diff --git a/base/allocator/partition_alloc_features.cc b/base/allocator/partition_alloc_features.cc
index 8cae69412..425f428b3 100644
--- a/base/allocator/partition_alloc_features.cc
+++ b/base/allocator/partition_alloc_features.cc
@@ -162,11 +162,18 @@ const base::FeatureParam<bool> kBackupRefPtrAsanEnableExtractionCheckParam{
const base::FeatureParam<bool> kBackupRefPtrAsanEnableInstantiationCheckParam{
&kPartitionAllocBackupRefPtr, "asan-enable-instantiation-check", true};
-// If enabled, switches the bucket distribution to an alternate one. Only one of
-// these features may b e enabled at a time.
+// If enabled, switches the bucket distribution to an alternate one.
+//
+// We enable this by default everywhere except for 32-bit Android, since we saw
+// regressions there.
BASE_FEATURE(kPartitionAllocUseAlternateDistribution,
"PartitionAllocUseAlternateDistribution",
- FEATURE_DISABLED_BY_DEFAULT);
+#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS)
+ FEATURE_DISABLED_BY_DEFAULT
+#else
+ FEATURE_ENABLED_BY_DEFAULT
+#endif // BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS)
+);
const base::FeatureParam<AlternateBucketDistributionMode>::Option
kPartitionAllocAlternateDistributionOption[] = {
{AlternateBucketDistributionMode::kDefault, "default"},
diff --git a/base/allocator/partition_alloc_support.cc b/base/allocator/partition_alloc_support.cc
index 4a48f7678..d2d56a9ad 100644
--- a/base/allocator/partition_alloc_support.cc
+++ b/base/allocator/partition_alloc_support.cc
@@ -39,6 +39,7 @@
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/thread_annotations.h"
#include "base/threading/platform_thread.h"
@@ -1077,6 +1078,13 @@ void PartitionAllocSupport::ReconfigureAfterFeatureListInit(
#endif // BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
+ // No specified type means we are in the browser.
+ auto bucket_distribution =
+ process_type == ""
+ ? base::features::kPartitionAllocAlternateBucketDistributionParam
+ .Get()
+ : base::features::AlternateBucketDistributionMode::kDefault;
+
allocator_shim::ConfigurePartitions(
allocator_shim::EnableBrp(brp_config.enable_brp),
allocator_shim::EnableBrpZapping(brp_config.enable_brp_zapping),
@@ -1086,9 +1094,7 @@ void PartitionAllocSupport::ReconfigureAfterFeatureListInit(
allocator_shim::UseDedicatedAlignedPartition(
brp_config.use_dedicated_aligned_partition),
allocator_shim::AddDummyRefCount(brp_config.add_dummy_ref_count),
- allocator_shim::AlternateBucketDistribution(
- base::features::kPartitionAllocAlternateBucketDistributionParam
- .Get()));
+ allocator_shim::AlternateBucketDistribution(bucket_distribution));
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// If BRP is not enabled, check if any of PCScan flags is enabled.
@@ -1194,7 +1200,7 @@ void PartitionAllocSupport::ReconfigureAfterTaskRunnerInit(
#if BUILDFLAG(IS_ANDROID)
// Lower thread cache limits to avoid stranding too much memory in the caches.
- if (base::SysInfo::IsLowEndDevice()) {
+ if (base::SysInfo::IsLowEndDeviceOrPartialLowEndModeEnabled()) {
::partition_alloc::ThreadCacheRegistry::Instance().SetThreadCacheMultiplier(
::partition_alloc::ThreadCache::kDefaultMultiplier / 2.);
}
diff --git a/base/allocator/partition_allocator/BUILD.gn b/base/allocator/partition_allocator/BUILD.gn
index 6e79959df..4d64e3fdb 100644
--- a/base/allocator/partition_allocator/BUILD.gn
+++ b/base/allocator/partition_allocator/BUILD.gn
@@ -427,6 +427,7 @@ buildflag_header("partition_alloc_buildflags") {
"ENABLE_BACKUP_REF_PTR_SUPPORT=$enable_backup_ref_ptr_support",
"ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=$enable_backup_ref_ptr_slow_checks",
"ENABLE_BACKUP_REF_PTR_FEATURE_FLAG=$enable_backup_ref_ptr_feature_flag",
+ "ENABLE_RAW_PTR_EXPERIMENTAL=$enable_raw_ptr_experimental",
"ENABLE_DANGLING_RAW_PTR_CHECKS=$enable_dangling_raw_ptr_checks",
"ENABLE_DANGLING_RAW_PTR_FEATURE_FLAG=$enable_dangling_raw_ptr_feature_flag",
"ENABLE_DANGLING_RAW_PTR_PERF_EXPERIMENT=$enable_dangling_raw_ptr_perf_experiment",
diff --git a/base/allocator/partition_allocator/page_allocator_internals_posix.h b/base/allocator/partition_allocator/page_allocator_internals_posix.h
index c7ab6de0b..30009c960 100644
--- a/base/allocator/partition_allocator/page_allocator_internals_posix.h
+++ b/base/allocator/partition_allocator/page_allocator_internals_posix.h
@@ -35,7 +35,7 @@
#include <Security/Security.h>
#include <mach/mach.h>
#endif
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
#include <sys/prctl.h>
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
@@ -62,7 +62,8 @@ namespace partition_alloc::internal {
namespace {
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
+#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
const char* PageTagToName(PageTag tag) {
// Important: All the names should be string literals. As per prctl.h in
// //third_party/android_ndk the kernel keeps a pointer to the name instead
@@ -84,6 +85,7 @@ const char* PageTagToName(PageTag tag) {
return "";
}
}
+#endif
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_MAC)
@@ -195,15 +197,17 @@ uintptr_t SystemAllocPagesInternal(uintptr_t hint,
ret = nullptr;
}
-#if BUILDFLAG(IS_ANDROID)
- // On Android, anonymous mappings can have a name attached to them. This is
- // useful for debugging, and double-checking memory attribution.
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
+#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
+ // On Android and Linux, anonymous mappings can have a name attached to them.
+ // This is useful for debugging, and double-checking memory attribution.
if (ret) {
// No error checking on purpose, testing only.
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ret, length,
PageTagToName(page_tag));
}
#endif
+#endif
return reinterpret_cast<uintptr_t>(ret);
}
diff --git a/base/allocator/partition_allocator/partition_alloc.gni b/base/allocator/partition_allocator/partition_alloc.gni
index 7a431fd46..558c3820c 100644
--- a/base/allocator/partition_allocator/partition_alloc.gni
+++ b/base/allocator/partition_allocator/partition_alloc.gni
@@ -15,7 +15,8 @@ if (is_apple) {
if (is_nacl) {
# NaCl targets don't use 64-bit pointers.
has_64_bit_pointers = false
-} else if (current_cpu == "x64" || current_cpu == "arm64") {
+} else if (current_cpu == "x64" || current_cpu == "arm64" ||
+ current_cpu == "loong64") {
has_64_bit_pointers = true
} else if (current_cpu == "x86" || current_cpu == "arm") {
has_64_bit_pointers = false
@@ -94,6 +95,20 @@ declare_args() {
force_enable_raw_ptr_exclusion = false
}
+declare_args() {
+ # Determines whether `raw_ptr_experimental<T>` is an alias for
+ # `raw_ptr<T>` or `T*` (true raw pointer).
+ #
+ # Members rewritten as `raw_ptr_experimental` rely on this as an
+ # escape hatch to degrade to a `T*` if `raw_ptr` performance proves
+ # problematic. Defaults to match standard `raw_ptr` support.
+ #
+ # One side effect of this is that `raw_ptr_experimental<T> foo_` must
+ # not use `foo_.get()`; this is incoherent when `foo_` is a `T*`.
+ # Use `base::to_address()` instead.
+ enable_raw_ptr_experimental = enable_backup_ref_ptr_support
+}
+
assert(!enable_pointer_compression_support || glue_core_pools,
"Pointer compression relies on core pools being contiguous.")
@@ -195,6 +210,7 @@ if (is_nacl) {
if (!use_partition_alloc) {
use_partition_alloc_as_malloc = false
enable_backup_ref_ptr_support = false
+ enable_raw_ptr_experimental = false
use_asan_backup_ref_ptr = false
use_asan_unowned_ptr = false
use_hookable_raw_ptr = false
@@ -218,6 +234,10 @@ assert(
assert(enable_backup_ref_ptr_support || !enable_backup_ref_ptr_slow_checks,
"Can't enable additional BackupRefPtr checks if it isn't enabled at all")
+assert(
+ enable_backup_ref_ptr_support || !enable_raw_ptr_experimental,
+ "Can't make `raw_ptr_experimental` = `raw_ptr` when the latter is wholly disabled")
+
# enable_dangling_raw_ptr_checks can only be used if enable_backup_ref_ptr_support
# is true.
assert(
diff --git a/base/allocator/partition_allocator/partition_alloc_hooks.cc b/base/allocator/partition_allocator/partition_alloc_hooks.cc
index 79c4ccfe3..0de4de448 100644
--- a/base/allocator/partition_allocator/partition_alloc_hooks.cc
+++ b/base/allocator/partition_allocator/partition_alloc_hooks.cc
@@ -124,6 +124,8 @@ bool PartitionAllocHooks::ReallocOverrideHookIfEnabled(size_t* out,
return false;
}
+// Do not unset the hook if there are remaining quarantined slots
+// not to break checks on unquarantining.
void PartitionAllocHooks::SetQuarantineOverrideHook(
QuarantineOverrideHook* hook) {
quarantine_override_hook_.store(hook, std::memory_order_release);
diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h
index c52453fce..c1a797ade 100644
--- a/base/allocator/partition_allocator/partition_root.h
+++ b/base/allocator/partition_allocator/partition_root.h
@@ -436,11 +436,6 @@ struct PA_ALIGNAS(64) PA_COMPONENT_EXPORT(PARTITION_ALLOC) PartitionRoot {
PA_ALWAYS_INLINE static PartitionRoot* FromAddrInFirstSuperpage(
uintptr_t address);
- PA_ALWAYS_INLINE void DecreaseTotalSizeOfAllocatedBytes(SlotSpan* slot_span)
- PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
- PA_ALWAYS_INLINE void IncreaseTotalSizeOfAllocatedBytes(SlotSpan* slot_span,
- size_t raw_size)
- PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
PA_ALWAYS_INLINE void DecreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
size_t len)
PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
@@ -1063,8 +1058,18 @@ PA_ALWAYS_INLINE void PartitionAllocFreeForRefCounting(uintptr_t slot_start) {
// supports reference counts.
PA_DCHECK(root->brp_enabled());
- // memset() can be really expensive.
+ // Iterating over the entire slot can be really expensive.
#if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
+ auto hook = PartitionAllocHooks::GetQuarantineOverrideHook();
+ // If we have a hook the object segment is not necessarily filled
+ // with |kQuarantinedByte|.
+ if (PA_LIKELY(!hook)) {
+ unsigned char* object =
+ static_cast<unsigned char*>(root->SlotStartToObject(slot_start));
+ for (size_t i = 0; i < slot_span->GetUsableSize(root); ++i) {
+ PA_DCHECK(object[i] == kQuarantinedByte);
+ }
+ }
DebugMemset(SlotStartAddr2Ptr(slot_start), kFreedByte,
slot_span->GetUtilizedSlotSize()
#if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT)
@@ -1145,7 +1150,8 @@ PartitionRoot<thread_safe>::AllocFromBucket(Bucket* bucket,
*usable_size = slot_span->GetUsableSize(this);
}
PA_DCHECK(slot_span->GetUtilizedSlotSize() <= slot_span->bucket->slot_size);
- IncreaseTotalSizeOfAllocatedBytes(slot_span, raw_size);
+ IncreaseTotalSizeOfAllocatedBytes(
+ slot_start, slot_span->GetSlotSizeForBookkeeping(), raw_size);
#if BUILDFLAG(USE_FREESLOT_BITMAP)
if (!slot_span->bucket->is_direct_mapped()) {
@@ -1414,12 +1420,15 @@ template <bool thread_safe>
PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::FreeInSlotSpan(
uintptr_t slot_start,
SlotSpan* slot_span) {
- DecreaseTotalSizeOfAllocatedBytes(slot_span);
+ DecreaseTotalSizeOfAllocatedBytes(slot_start,
+ slot_span->GetSlotSizeForBookkeeping());
+
#if BUILDFLAG(USE_FREESLOT_BITMAP)
if (!slot_span->bucket->is_direct_mapped()) {
internal::FreeSlotBitmapMarkSlotAsFree(slot_start);
}
#endif
+
return slot_span->Free(slot_start);
}
@@ -1498,7 +1507,11 @@ PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RawFreeBatch(
// corresponding pages were faulted in (without acquiring the lock). So there
// is no need to touch pages manually here before the lock.
::partition_alloc::internal::ScopedGuard guard{lock_};
- DecreaseTotalSizeOfAllocatedBytes(slot_span);
+ // TODO(thiabaud): Fix the accounting here. The size is correct, but the
+ // pointer is not. This only affects local tools that record each allocation,
+ // not our metrics.
+ DecreaseTotalSizeOfAllocatedBytes(
+ 0u, slot_span->GetSlotSizeForBookkeeping() * size);
slot_span->AppendFreeList(head, tail, size);
}
@@ -1586,24 +1599,6 @@ PartitionRoot<thread_safe>::FromAddrInFirstSuperpage(uintptr_t address) {
template <bool thread_safe>
PA_ALWAYS_INLINE void
-PartitionRoot<thread_safe>::IncreaseTotalSizeOfAllocatedBytes(
- SlotSpan* slot_span,
- size_t raw_size) {
- IncreaseTotalSizeOfAllocatedBytes(reinterpret_cast<uintptr_t>(slot_span),
- slot_span->GetSlotSizeForBookkeeping(),
- raw_size);
-}
-
-template <bool thread_safe>
-PA_ALWAYS_INLINE void
-PartitionRoot<thread_safe>::DecreaseTotalSizeOfAllocatedBytes(
- SlotSpan* slot_span) {
- DecreaseTotalSizeOfAllocatedBytes(reinterpret_cast<uintptr_t>(slot_span),
- slot_span->GetSlotSizeForBookkeeping());
-}
-
-template <bool thread_safe>
-PA_ALWAYS_INLINE void
PartitionRoot<thread_safe>::IncreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
size_t len,
size_t raw_size) {
diff --git a/base/allocator/partition_allocator/pointers/raw_ptr.h b/base/allocator/partition_allocator/pointers/raw_ptr.h
index 9ff1d2130..1b83201db 100644
--- a/base/allocator/partition_allocator/pointers/raw_ptr.h
+++ b/base/allocator/partition_allocator/pointers/raw_ptr.h
@@ -160,6 +160,14 @@ struct TraitsToImpl;
template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
class raw_ptr;
+#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
+template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
+using raw_ptr_experimental = raw_ptr<T, Traits>;
+#else
+template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty>
+using raw_ptr_experimental = T*;
+#endif // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
+
} // namespace base
// This type is to be used internally, or in callbacks arguments when it is
@@ -1127,6 +1135,7 @@ using RemovePointerT = typename RemovePointer<T>::type;
} // namespace base
using base::raw_ptr;
+using base::raw_ptr_experimental;
// DisableDanglingPtrDetection option for raw_ptr annotates
// "intentional-and-safe" dangling pointers. It is meant to be used at the
@@ -1149,6 +1158,17 @@ constexpr auto DanglingUntriaged = base::RawPtrTraits::kMayDangle;
// instead of the raw_ptr.
constexpr auto AllowPtrArithmetic = base::RawPtrTraits::kAllowPtrArithmetic;
+// Temporary flag for `raw_ptr` / `raw_ref`. This is used by finch experiments
+// to differentiate pointers added recently for the ChromeOS ash rewrite.
+//
+// See launch plan:
+// https://docs.google.com/document/d/105OVhNl-2lrfWElQSk5BXYv-nLynfxUrbC4l8cZ0CoU/edit#
+//
+// This is not meant to be added manually. You can ignore this flag.
+//
+// TODO(https://crbug.com/1435441) Implement the ExperimentalAsh Trait.
+constexpr auto ExperimentalAsh = base::RawPtrTraits::kMayDangle;
+
namespace std {
// Override so set/map lookups do not create extra raw_ptr. This also allows
diff --git a/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc b/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc
index 5508c9cd1..7644478a2 100644
--- a/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc
+++ b/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc
@@ -6,6 +6,7 @@
#include <climits>
#include <cstddef>
+#include <cstdint>
#include <string>
#include <thread>
#include <type_traits>
@@ -25,6 +26,7 @@
#include "base/allocator/partition_allocator/pointers/raw_ref.h"
#include "base/allocator/partition_allocator/tagging.h"
#include "base/cpu.h"
+#include "base/cxx20_to_address.h"
#include "base/logging.h"
#include "base/memory/raw_ptr_asan_service.h"
#include "base/task/thread_pool.h"
@@ -1475,6 +1477,59 @@ TEST_F(RawPtrTest, CrossKindAssignment) {
CountersMatch());
}
+// Without the explicitly customized `raw_ptr::to_address()`,
+// `base::to_address()` will use the dereference operator. This is not
+// what we want; this test enforces extraction semantics for
+// `to_address()`.
+TEST_F(RawPtrTest, ToAddressDoesNotDereference) {
+ CountingRawPtr<int> ptr = nullptr;
+ int* raw = base::to_address(ptr);
+ std::ignore = raw;
+ EXPECT_THAT((CountingRawPtrExpectations<RawPtrCountingImpl>{
+ .get_for_dereference_cnt = 0,
+ .get_for_extraction_cnt = 1,
+ .get_for_comparison_cnt = 0,
+ .get_for_duplication_cnt = 0}),
+ CountersMatch());
+}
+
+TEST_F(RawPtrTest, ToAddressGivesBackRawAddress) {
+ int* raw = nullptr;
+ raw_ptr<int> miracle = raw;
+ EXPECT_EQ(base::to_address(raw), base::to_address(miracle));
+}
+
+// Verifies that `raw_ptr_experimental` is aliased appropriately.
+//
+// The `DisableDanglingPtrDetection` trait is arbitrarily chosen and is
+// just there to ensure that `raw_ptr_experimental` knows how to field
+// the traits template argument.
+#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
+static_assert(
+ std::is_same_v<raw_ptr_experimental<int, DisableDanglingPtrDetection>,
+ raw_ptr<int, DisableDanglingPtrDetection>>);
+static_assert(
+ std::is_same_v<raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
+ raw_ptr<const int, DisableDanglingPtrDetection>>);
+static_assert(
+ std::is_same_v<
+ const raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
+ const raw_ptr<const int, DisableDanglingPtrDetection>>);
+#else // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
+// `DisableDanglingPtrDetection` means nothing here and is silently
+// ignored.
+static_assert(
+ std::is_same_v<raw_ptr_experimental<int, DisableDanglingPtrDetection>,
+ int*>);
+static_assert(
+ std::is_same_v<raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
+ const int*>);
+static_assert(
+ std::is_same_v<
+ const raw_ptr_experimental<const int, DisableDanglingPtrDetection>,
+ const int* const>);
+#endif // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL)
+
} // namespace
namespace base {
@@ -2122,6 +2177,28 @@ TEST_F(BackupRefPtrTest, Duplicate) {
}
#endif // BUILDFLAG(BACKUP_REF_PTR_POISON_OOB_PTR)
+#if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
+TEST_F(BackupRefPtrTest, WriteAfterFree) {
+ constexpr uint64_t kPayload = 0x1234567890ABCDEF;
+
+ raw_ptr<uint64_t> ptr =
+ static_cast<uint64_t*>(allocator_.root()->Alloc(sizeof(uint64_t), ""));
+
+ // Now |ptr| should be quarantined.
+ allocator_.root()->Free(ptr);
+
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ // Write something different from |kQuarantinedByte|.
+ *ptr = kPayload;
+ // Write-after-Free should lead to crash
+ // on |PartitionAllocFreeForRefCounting|.
+ ptr = nullptr;
+ },
+ "");
+}
+#endif // BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
+
namespace {
constexpr uint8_t kCustomQuarantineByte = 0xff;
static_assert(kCustomQuarantineByte !=
@@ -2138,13 +2215,20 @@ TEST_F(BackupRefPtrTest, QuarantineHook) {
uint8_t* native_ptr =
static_cast<uint8_t*>(allocator_.root()->Alloc(sizeof(uint8_t), ""));
*native_ptr = 0;
- raw_ptr<uint8_t> smart_ptr = native_ptr;
-
- allocator_.root()->Free(smart_ptr);
- // Access the allocation through the native pointer to avoid triggering
- // dereference checks in debug builds.
- EXPECT_EQ(*partition_alloc::internal::TagPtr(native_ptr),
- kCustomQuarantineByte);
+ {
+ raw_ptr<uint8_t> smart_ptr = native_ptr;
+
+ allocator_.root()->Free(smart_ptr);
+ // Access the allocation through the native pointer to avoid triggering
+ // dereference checks in debug builds.
+ EXPECT_EQ(*partition_alloc::internal::TagPtr(native_ptr),
+ kCustomQuarantineByte);
+
+ // Leaving |smart_ptr| filled with |kCustomQuarantineByte| can
+ // cause a crash because we have a DCHECK that expects it to be filled with
+ // |kQuarantineByte|. We need to ensure it is unquarantined before
+ // unregistering the hook.
+ } // <- unquarantined here
partition_alloc::PartitionAllocHooks::SetQuarantineOverrideHook(nullptr);
}
diff --git a/base/android/field_trial_list.cc b/base/android/field_trial_list.cc
index 64b176352..d2b01fcaf 100644
--- a/base/android/field_trial_list.cc
+++ b/base/android/field_trial_list.cc
@@ -11,6 +11,7 @@
#include "base/base_jni_headers/FieldTrialList_jni.h"
#include "base/lazy_instance.h"
#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_list_including_low_anonymity.h"
#include "base/metrics/field_trial_params.h"
using base::android::ConvertJavaStringToUTF8;
@@ -77,15 +78,39 @@ static ScopedJavaLocalRef<jstring> JNI_FieldTrialList_GetVariationParameter(
env, parameters[ConvertJavaStringToUTF8(env, jparameter_key)]);
}
+// JNI_FieldTrialList_LogActiveTrials() is static function, this makes friending
+// it a hassle because it must be declared in the file that the friend
+// declaration is in, but its declaration can't be included in multiple places
+// or things get messy and the linker gets mad. This helper class exists only to
+// friend the JNI function and is, in turn, friended by
+// FieldTrialListIncludingLowAnonymity which allows for the private
+// GetActiveFieldTrialGroups() to be reached.
+class AndroidFieldTrialListLogActiveTrialsFriendHelper {
+ private:
+ friend void ::JNI_FieldTrialList_LogActiveTrials(JNIEnv* env);
+
+ static bool AddObserver(base::FieldTrialList::Observer* observer) {
+ return base::FieldTrialListIncludingLowAnonymity::AddObserver(observer);
+ }
+
+ static void GetActiveFieldTrialGroups(
+ base::FieldTrial::ActiveGroups* active_groups) {
+ base::FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups(
+ active_groups);
+ }
+};
+
static void JNI_FieldTrialList_LogActiveTrials(JNIEnv* env) {
DCHECK(!g_trial_logger.IsCreated()); // This need only be called once.
LOG(INFO) << "Logging active field trials...";
- base::FieldTrialList::AddObserver(&g_trial_logger.Get());
+ AndroidFieldTrialListLogActiveTrialsFriendHelper::AddObserver(
+ &g_trial_logger.Get());
// Log any trials that were already active before adding the observer.
std::vector<base::FieldTrial::ActiveGroup> active_groups;
- base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
+ AndroidFieldTrialListLogActiveTrialsFriendHelper::GetActiveFieldTrialGroups(
+ &active_groups);
for (const base::FieldTrial::ActiveGroup& group : active_groups) {
TrialLogger::Log(group.trial_name, group.group_name);
}
diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java
index 1b8658001..6c81339f0 100644
--- a/base/android/java/src/org/chromium/base/BuildInfo.java
+++ b/base/android/java/src/org/chromium/base/BuildInfo.java
@@ -287,14 +287,11 @@ public class BuildInfo {
public static boolean targetsAtLeastU() {
int target = ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion;
- // Logic for pre-API-finalization:
- return SdkLevel.isAtLeastU() && target == Build.VERSION_CODES.CUR_DEVELOPMENT;
-
// Logic for after API finalization but before public SDK release has to
// just hardcode the appropriate SDK integer. This will include Android
// builds with the finalized SDK, and also pre-API-finalization builds
// (because CUR_DEVELOPMENT == 10000).
- // return target >= <integer placeholder for U>;
+ return target >= 34;
// Once the public SDK is upstreamed we can use the defined constant,
// deprecate this, then eventually inline this at all callsites and
diff --git a/base/android/java/src/org/chromium/base/ByteArrayGenerator.java b/base/android/java/src/org/chromium/base/ByteArrayGenerator.java
deleted file mode 100644
index 71749d516..000000000
--- a/base/android/java/src/org/chromium/base/ByteArrayGenerator.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// 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.FileInputStream;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
-/**
- * Reads /dev/urandom to generate byte arrays for used in cryptographic functions.
- */
-public class ByteArrayGenerator {
- /**
- * Polls random data to generate the array.
- * @param numBytes Length of the array to generate.
- * @return byte[] containing randomly generated data.
- */
- public byte[] getBytes(int numBytes) throws IOException, GeneralSecurityException {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream("/dev/urandom");
- byte[] bytes = new byte[numBytes];
- if (bytes.length != fis.read(bytes)) {
- throw new GeneralSecurityException("Not enough random data available");
- }
- return bytes;
- } finally {
- if (fis != null) {
- fis.close();
- }
- }
- }
-}
diff --git a/base/android/java/src/org/chromium/base/ResettersForTesting.java b/base/android/java/src/org/chromium/base/ResettersForTesting.java
new file mode 100644
index 000000000..ecd25b0d1
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ResettersForTesting.java
@@ -0,0 +1,120 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+
+/**
+ * ResettersForTesting provides functionality for reset values set for testing. This class is used
+ * directly by test runners, but lives in prod code to simplify usage.
+ *
+ * It is required to invoke {@link #register(Runnable)} whenever a method called `set*ForTesting`,
+ * such `setFooForTesting(Foo foo)` is invoked. Typical usage looks like this:
+ *
+ * <code>
+ * class MyClass {
+ * private static MyClass sInstance;
+ *
+ * public static MyClass getInstance() {
+ * if (sInstance == null) sInstance = new MyClass();
+ * return sInstance;
+ * }
+ *
+ * public static void setMyClassForTesting(MyClass myClassObj) {
+ * sInstance = myClassObj;
+ * ResettersForTesting.register(() -> sInstance = null);
+ * }
+ * }
+ * </code>
+ *
+ * This is not only used for singleton instances, but can also be used for resetting other static
+ * members.
+ *
+ * <code>
+ * class NeedsFoo {
+ * private static Foo sFooForTesting;
+ *
+ * public void doThing() {
+ * Foo foo;
+ * if (sFooForTesting != null) {
+ * foo = sFooForTesting;
+ * } else {* foo = new FooImpl();
+ * }
+ * foo.doItsThing();
+ * }
+ *
+ * public static void setFooForTesting(Foo foo) {
+ * sFooForTesting = foo;
+ * ResettersForTesting.register(() -> sFooForTesting = null);
+ * }
+ * }
+ * </code>
+ *
+ * For cases where it is important that a particular resetter runs only once, even if the
+ * `set*ForTesting` method is invoked multiple times, there is another variation that can be used.
+ * In particular, since a lambda always ends up creating a new instance in Chromium builds, we can
+ * avoid this by having a single static instance of the resetter, like this:
+ *
+ * <code>
+ * private static class NeedsFooSingleDestroy {
+ * private static final class LazyHolder {
+ * private static Foo INSTANCE = new Foo();
+ * }
+ *
+ * private static LazyHolder sFoo;
+ *
+ * private static Runnable sOneShotResetter = () -> {
+ * sFoo.INSTANCE.destroy();
+ * sFoo = new Foo();
+ * };
+ *
+ * public static void setFooForTesting(Foo foo) {
+ * sFoo.INSTANCE = foo;
+ * ResettersForTesting.register(sResetter);
+ * }
+ * }
+ * </code>
+ */
+public class ResettersForTesting {
+ // LinkedHashSet is a set that provides ordering and enables one-shot resetters to only be
+ // invoked once. For example, the following `sResetter` will only be in the set a single time.
+ // <code>
+ // private static final Runnable sResetter = () -> { ... }
+ // ...
+ // ResettersForTesting.register(sResetter);
+ // </code>
+ private static final LinkedHashSet<Runnable> sResetters = new LinkedHashSet<>();
+
+ /**
+ * Register a {@link Runnable} that will automatically execute during test tear down.
+ * @param runnable the {@link Runnable} to execute.
+ */
+ public static void register(Runnable runnable) {
+ synchronized (sResetters) {
+ sResetters.add(runnable);
+ }
+ }
+
+ /**
+ * Execute and clear all the currently registered resetters.
+ *
+ * This is not intended to be invoked manually, but is intended to be invoked by the test
+ * runners automatically during tear down.
+ */
+ public static void executeResetters() {
+ ArrayList<Runnable> resetters;
+ synchronized (sResetters) {
+ resetters = new ArrayList<>(sResetters);
+ sResetters.clear();
+ }
+
+ // Ensure that resetters are run in reverse order, enabling nesting of values as well as
+ // being more similar to C++ destruction order.
+ Collections.reverse(resetters);
+ for (Runnable resetter : resetters) resetter.run();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/ThreadUtils.java b/base/android/java/src/org/chromium/base/ThreadUtils.java
index d5b7ee21e..aab0888d5 100644
--- a/base/android/java/src/org/chromium/base/ThreadUtils.java
+++ b/base/android/java/src/org/chromium/base/ThreadUtils.java
@@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -24,9 +25,9 @@ public class ThreadUtils {
private static final Object sLock = new Object();
- private static boolean sWillOverride;
+ private static volatile boolean sWillOverride;
- private static Handler sUiThreadHandler;
+ private static volatile Handler sUiThreadHandler;
private static boolean sThreadAssertsDisabled;
@@ -75,51 +76,45 @@ public class ThreadUtils {
}
}
- public static void setWillOverrideUiThread(boolean willOverrideUiThread) {
- synchronized (sLock) {
- sWillOverride = willOverrideUiThread;
- }
+ public static void setWillOverrideUiThread() {
+ sWillOverride = true;
+ assert sUiThreadHandler == null;
}
@VisibleForTesting
public static void clearUiThreadForTesting() {
- synchronized (sLock) {
- sUiThreadHandler = null;
- sWillOverride = false;
- PostTask.resetUiThreadForTesting(); // IN-TEST
- }
+ sWillOverride = false;
+ PostTask.resetUiThreadForTesting(); // IN-TEST
+ sUiThreadHandler = null;
}
public static void setUiThread(Looper looper) {
+ assert looper != null;
synchronized (sLock) {
- assert looper != null;
- if (sUiThreadHandler != null && sUiThreadHandler.getLooper() != looper) {
+ if (sUiThreadHandler == null) {
+ Handler uiThreadHandler = new Handler(looper);
+ // Set up the UI Thread TaskExecutor before signaling readiness.
+ PostTask.onUiThreadReady(uiThreadHandler);
+ // volatile write signals readiness since other threads read it without acquiring
+ // sLock.
+ sUiThreadHandler = uiThreadHandler;
+ // Must come after PostTask is initialized since it uses PostTask.
+ TraceEvent.onUiThreadReady();
+ } else if (sUiThreadHandler.getLooper() != looper) {
throw new RuntimeException("UI thread looper is already set to "
+ sUiThreadHandler.getLooper() + " (Main thread looper is "
+ Looper.getMainLooper() + "), cannot set to new looper " + looper);
- } else {
- sUiThreadHandler = new Handler(looper);
}
- PostTask.onUiThreadReady();
}
- TraceEvent.onUiThreadReady();
}
public static Handler getUiThreadHandler() {
- boolean createdHandler = false;
- synchronized (sLock) {
- if (sUiThreadHandler == null) {
- if (sWillOverride) {
- throw new RuntimeException("Did not yet override the UI thread");
- }
- sUiThreadHandler = new Handler(Looper.getMainLooper());
- PostTask.onUiThreadReady();
- createdHandler = true;
- }
- }
- if (createdHandler) {
- TraceEvent.onUiThreadReady();
+ if (sUiThreadHandler != null) return sUiThreadHandler;
+
+ if (sWillOverride) {
+ throw new RuntimeException("Did not yet override the UI thread");
}
+ setUiThread(Looper.getMainLooper());
return sUiThreadHandler;
}
@@ -127,42 +122,25 @@ public class ThreadUtils {
* Run the supplied Runnable on the main thread. The method will block until the Runnable
* completes.
*
- * @deprecated Use {@link
- * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Runnable)
- * TestThreadUtils.runOnUiThreadBlocking(r)} instead. For non-test usage (heavily
- * discouraged) use {@link org.chromium.base.task.PostTask#runSynchronously(TaskTraits,
- * Runnable) PostTask.runSynchronously(TaskTraits, Runnable)} with task traits chosen from
- * {@link org.chromium.base.task.TaskTraits}. If the call site can't import
- * content, it means it shouldn't be posting to the UI thread at all; all such usages will
- * gradually get rewritten.
+ * Note that non-test usage of this function is heavily discouraged. For non-tests, use
+ * callbacks rather than blocking threads.
+ *
* @param r The Runnable to run.
*/
- @Deprecated
public static void runOnUiThreadBlocking(final Runnable r) {
- if (runningOnUiThread()) {
- r.run();
- } else {
- FutureTask<Void> task = new FutureTask<Void>(r, null);
- postOnUiThread(task);
- try {
- task.get();
- } catch (Exception e) {
- throw new RuntimeException("Exception occurred while waiting for runnable", e);
- }
- }
+ PostTask.runSynchronously(TaskTraits.UI_DEFAULT, r);
}
/**
* Run the supplied Callable on the main thread, wrapping any exceptions in a RuntimeException.
* The method will block until the Callable completes.
*
- * @deprecated Use {@link
- * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlockingNoException(Callable)
- * TestThreadUtils.runOnUiThreadBlockingNoException(c)} instead.
+ * Note that non-test usage of this function is heavily discouraged. For non-tests, use
+ * callbacks rather than blocking threads.
+ *
* @param c The Callable to run
* @return The result of the callable
*/
- @Deprecated
public static <T> T runOnUiThreadBlockingNoException(Callable<T> c) {
try {
return runOnUiThreadBlocking(c);
@@ -175,43 +153,26 @@ public class ThreadUtils {
* Run the supplied Callable on the main thread, The method will block until the Callable
* completes.
*
- * @deprecated Use {@link
- * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Callable)
- * TestThreadUtils.runOnUiThreadBlocking(c)} instead.
+ * Note that non-test usage of this function is heavily discouraged. For non-tests, use
+ * callbacks rather than blocking threads.
+ *
* @param c The Callable to run
* @return The result of the callable
* @throws ExecutionException c's exception
*/
- @Deprecated
public static <T> T runOnUiThreadBlocking(Callable<T> c) throws ExecutionException {
- FutureTask<T> task = new FutureTask<T>(c);
- runOnUiThread(task);
- try {
- return task.get();
- } catch (InterruptedException e) {
- throw new RuntimeException("Interrupted waiting for callable", e);
- }
+ return PostTask.runSynchronously(TaskTraits.UI_DEFAULT, c);
}
/**
* Run the supplied FutureTask on the main thread. The method will block only if the current
* thread is the main thread.
*
- * @deprecated Use {@link org.chromium.base.task.PostTask#runOrPostTask(TaskTraits, Runnable)
- * PostTask.runOrPostTask(TaskTraits, Runnable)} with task traits chosen from {@link
- * org.chromium.base.task.TaskTraits}.
- * If the call site can't import content, it means it shouldn't be posting to the UI
- * thread at all; all such usages will gradually get rewritten.
* @param task The FutureTask to run
* @return The queried task (to aid inline construction)
*/
- @Deprecated
public static <T> FutureTask<T> runOnUiThread(FutureTask<T> task) {
- if (runningOnUiThread()) {
- task.run();
- } else {
- postOnUiThread(task);
- }
+ PostTask.runOrPostTask(TaskTraits.UI_DEFAULT, task);
return task;
}
@@ -219,15 +180,9 @@ public class ThreadUtils {
* Run the supplied Callable on the main thread. The method will block only if the current
* thread is the main thread.
*
- * @deprecated Use {@link org.chromium.base.task.PostTask#runOrPostTask(TaskTraits, Runnable)
- * PostTask.runOrPostTask(TaskTraits, Runnable)} with task traits chosen from {@link
- * org.chromium.base.task.TaskTraits}.
- * If the call site can't import content, it means it shouldn't be posting to the UI
- * thread at all; all such usages will gradually get rewritten.
* @param c The Callable to run
* @return A FutureTask wrapping the callable to retrieve results
*/
- @Deprecated
public static <T> FutureTask<T> runOnUiThread(Callable<T> c) {
return runOnUiThread(new FutureTask<T>(c));
}
@@ -236,37 +191,21 @@ public class ThreadUtils {
* Run the supplied Runnable on the main thread. The method will block only if the current
* thread is the main thread.
*
- * @deprecated Use {@link org.chromium.base.task.PostTask#runOrPostTask(TaskTraits, Runnable)
- * PostTask.runOrPostTask(TaskTraits, Runnable)} with task traits chosen from {@link
- * org.chromium.base.task.TaskTraits}.
- * If the call site can't import content, it means it shouldn't be posting to the UI
- * thread at all; all such usages will gradually get rewritten.
* @param r The Runnable to run
*/
- @Deprecated
public static void runOnUiThread(Runnable r) {
- if (runningOnUiThread()) {
- r.run();
- } else {
- getUiThreadHandler().post(r);
- }
+ PostTask.runOrPostTask(TaskTraits.UI_DEFAULT, r);
}
/**
* Post the supplied FutureTask to run on the main thread. The method will not block, even if
* called on the UI thread.
*
- * @deprecated Use {@link org.chromium.base.task.PostTask#postTask(TaskTraits, Runnable)
- * PostTask.postTask(TaskTraits, Runnable)} with task traits chosen from {@link
- * org.chromium.base.task.TaskTraits}.
- * If the call site can't import content, it means it shouldn't be posting to the UI
- * thread at all; all such usages will gradually get rewritten.
* @param task The FutureTask to run
* @return The queried task (to aid inline construction)
*/
- @Deprecated
public static <T> FutureTask<T> postOnUiThread(FutureTask<T> task) {
- getUiThreadHandler().post(task);
+ PostTask.postTask(TaskTraits.UI_DEFAULT, task);
return task;
}
@@ -274,33 +213,21 @@ public class ThreadUtils {
* Post the supplied Runnable to run on the main thread. The method will not block, even if
* called on the UI thread.
*
- * @deprecated Use {@link org.chromium.base.task.PostTask#postTask(TaskTraits, Runnable)
- * PostTask.postTask(TaskTraits, Runnable)} with task traits chosen from {@link
- * org.chromium.base.task.TaskTraits}.
- * If the call site can't import content, it means it shouldn't be posting to the UI
- * thread at all; all such usages will gradually get rewritten.
- * @param task The Runnable to run
+ * @param r The Runnable to run
*/
- @Deprecated
- public static void postOnUiThread(Runnable task) {
- getUiThreadHandler().post(task);
+ public static void postOnUiThread(Runnable r) {
+ PostTask.postTask(TaskTraits.UI_DEFAULT, r);
}
/**
* Post the supplied Runnable to run on the main thread after the given amount of time. The
* method will not block, even if called on the UI thread.
*
- * @deprecated Use {@link org.chromium.base.task.PostTask#postDelayedTask(TaskTraits, Runnable,
- * long) PostTask.postDelayedTask(TaskTraits, Runnable, long)} with task traits chosen
- * from {@link org.chromium.base.task.TaskTraits}.
- * If the call site can't import content, it means it shouldn't be posting to the UI
- * thread at all; all such usages will gradually get rewritten.
- * @param task The Runnable to run
+ * @param r The Runnable to run
* @param delayMillis The delay in milliseconds until the Runnable will be run
*/
- @Deprecated
- public static void postOnUiThreadDelayed(Runnable task, long delayMillis) {
- getUiThreadHandler().postDelayed(task, delayMillis);
+ public static void postOnUiThreadDelayed(Runnable r, long delayMillis) {
+ PostTask.postDelayedTask(TaskTraits.UI_DEFAULT, r, delayMillis);
}
/**
diff --git a/base/android/java/src/org/chromium/base/TraceEvent.java b/base/android/java/src/org/chromium/base/TraceEvent.java
index e25550173..bbb57c23a 100644
--- a/base/android/java/src/org/chromium/base/TraceEvent.java
+++ b/base/android/java/src/org/chromium/base/TraceEvent.java
@@ -23,7 +23,6 @@ import org.chromium.base.task.TaskTraits;
import org.chromium.build.annotations.MainDex;
import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Java mirror of Chrome trace event API. See base/trace_event/trace_event.h.
@@ -45,8 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
@MainDex
public class TraceEvent implements AutoCloseable {
private static volatile boolean sEnabled; // True when tracing into Chrome's tracing service.
- private static AtomicBoolean sNativeTracingReady = new AtomicBoolean();
- private static AtomicBoolean sUiThreadReady = new AtomicBoolean();
+ private static volatile boolean sUiThreadReady;
private static boolean sEventNameFilteringEnabled;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -302,7 +300,7 @@ public class TraceEvent implements AutoCloseable {
ThreadUtils.getUiThreadLooper().setMessageLogging(
enabled ? LooperMonitorHolder.sInstance : null);
}
- if (sUiThreadReady.get()) {
+ if (sUiThreadReady) {
ViewHierarchyDumper.updateEnabledState();
}
}
@@ -335,15 +333,12 @@ public class TraceEvent implements AutoCloseable {
}
public static void onNativeTracingReady() {
- // Register an enabled observer, such that java traces are always
- // enabled with native.
- sNativeTracingReady.set(true);
TraceEventJni.get().registerEnabledObserver();
}
// Called by ThreadUtils.
static void onUiThreadReady() {
- sUiThreadReady.set(true);
+ sUiThreadReady = true;
if (sEnabled) {
ViewHierarchyDumper.updateEnabledState();
}
@@ -648,21 +643,18 @@ public class TraceEvent implements AutoCloseable {
}
public static void updateEnabledState() {
- if (!ThreadUtils.runningOnUiThread()) {
- ThreadUtils.postOnUiThread(() -> { updateEnabledState(); });
- return;
- }
-
- if (TraceEventJni.get().viewHierarchyDumpEnabled()) {
- if (sInstance == null) {
- sInstance = new ViewHierarchyDumper();
- }
- enable();
- } else {
- if (sInstance != null) {
- disable();
+ PostTask.runOrPostTask(TaskTraits.UI_DEFAULT, () -> {
+ if (TraceEventJni.get().viewHierarchyDumpEnabled()) {
+ if (sInstance == null) {
+ sInstance = new ViewHierarchyDumper();
+ }
+ enable();
+ } else {
+ if (sInstance != null) {
+ disable();
+ }
}
- }
+ });
}
private static void dumpView(ActivityInfo collection, int parentId, View v) {
diff --git a/base/android/java/src/org/chromium/base/WrappedClassLoader.java b/base/android/java/src/org/chromium/base/WrappedClassLoader.java
index ceff39578..45a6bb0c7 100644
--- a/base/android/java/src/org/chromium/base/WrappedClassLoader.java
+++ b/base/android/java/src/org/chromium/base/WrappedClassLoader.java
@@ -11,12 +11,12 @@ import dalvik.system.BaseDexClassLoader;
* to the first one that returns a match.
*/
public class WrappedClassLoader extends ClassLoader {
- private ClassLoader mPrimaryClassLoader;
- private ClassLoader mSecondaryClassLoader;
+ private final ClassLoader mPrimaryClassLoader;
+ private final ClassLoader mSecondaryClassLoader;
public WrappedClassLoader(ClassLoader primary, ClassLoader secondary) {
- this.mPrimaryClassLoader = primary;
- this.mSecondaryClassLoader = secondary;
+ mPrimaryClassLoader = primary;
+ mSecondaryClassLoader = secondary;
}
@Override
@@ -24,7 +24,12 @@ public class WrappedClassLoader extends ClassLoader {
try {
return mPrimaryClassLoader.loadClass(name);
} catch (ClassNotFoundException e) {
- return mSecondaryClassLoader.loadClass(name);
+ try {
+ return mSecondaryClassLoader.loadClass(name);
+ } catch (ClassNotFoundException e2) {
+ e.addSuppressed(e2);
+ throw e;
+ }
}
}
diff --git a/base/android/java/src/org/chromium/base/task/PostTask.java b/base/android/java/src/org/chromium/base/task/PostTask.java
index 7ff7ee64c..1f1e264d4 100644
--- a/base/android/java/src/org/chromium/base/task/PostTask.java
+++ b/base/android/java/src/org/chromium/base/task/PostTask.java
@@ -4,6 +4,8 @@
package org.chromium.base.task;
+import android.os.Handler;
+
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative;
@@ -130,11 +132,9 @@ public class PostTask {
*
* Use this only for trivial tasks as it ignores task priorities.
*
- * @deprecated In tests, use {@link
- * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Runnable)
- * TestThreadUtils.runOnUiThreadBlocking(Runnable)} instead. Non-test usage is heavily
- * discouraged. For non-tests, use callbacks rather than blocking threads. If you
- * absolutely must block the thread, use FutureTask.get().
+ * Note that non-test usage of this function is heavily discouraged. For non-tests, use
+ * callbacks rather than blocking threads.
+ *
* @param taskTraits The TaskTraits that describe the desired TaskRunner.
* @param task The task to be run with the specified traits.
* @return The result of the callable
@@ -154,11 +154,9 @@ public class PostTask {
*
* Use this only for trivial tasks as it ignores task priorities.
*
- * @deprecated In tests, use {@link
- * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Runnable)
- * TestThreadUtils.runOnUiThreadBlocking(Runnable)} instead. Non-test usage is heavily
- * discouraged. For non-tests, use callbacks rather than blocking threads. If you
- * absolutely must block the thread, use FutureTask.get().
+ * Note that non-test usage of this function is heavily discouraged. For non-tests, use
+ * callbacks rather than blocking threads.
+ *
* @param taskTraits The TaskTraits that describe the desired TaskRunner.
* @param task The task to be run with the specified traits.
*/
@@ -273,12 +271,15 @@ public class PostTask {
}
/** Called once when the UI thread has been initialized */
- public static void onUiThreadReady() {
+ public static void onUiThreadReady(Handler uiThreadHandler) {
assert sUiThreadTaskExecutor == null;
- sUiThreadTaskExecutor = new UiThreadTaskExecutor();
+ sUiThreadTaskExecutor = new UiThreadTaskExecutor(uiThreadHandler);
}
public static void resetUiThreadForTesting() {
+ // UI Thread cannot be reset cleanly after native initialization.
+ assert !sNativeInitialized;
+
sUiThreadTaskExecutor = null;
}
}
diff --git a/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java b/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java
index 2f349d85b..b02436670 100644
--- a/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java
+++ b/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java
@@ -49,4 +49,15 @@ public class SingleThreadTaskRunnerImpl extends TaskRunnerImpl implements Single
mHandler.post(mRunPreNativeTaskClosure);
}
}
+
+ @Override
+ protected boolean schedulePreNativeDelayedTask(Runnable task, long delay) {
+ if (mHandler == null) return false;
+ // In theory it would be fine to delay these tasks until native is initialized and post them
+ // to the native task runner, but in practice some tests don't initialize native and still
+ // expect delayed tasks to eventually run. There's no good reason not to support them here,
+ // there are so few of these tasks that they're very unlikely to cause performance problems.
+ mHandler.postDelayed(task, delay);
+ return true;
+ }
}
diff --git a/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java b/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java
index 21fd7fea3..0ab9c3991 100644
--- a/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java
+++ b/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java
@@ -161,7 +161,7 @@ public class TaskRunnerImpl implements TaskRunner {
if (delay == 0) {
mPreNativeTasks.add(task);
schedulePreNativeTask();
- } else {
+ } else if (!schedulePreNativeDelayedTask(task, delay)) {
Pair<Runnable, Long> preNativeDelayedTask = new Pair<>(task, delay);
mPreNativeDelayedTasks.add(preNativeDelayedTask);
}
@@ -201,6 +201,16 @@ public class TaskRunnerImpl implements TaskRunner {
}
/**
+ * Overridden in subclasses that support Delayed tasks pre-native.
+ *
+ * @return true if the task has been scheduled and does not need to be forwarded to the native
+ * task runner.
+ */
+ protected boolean schedulePreNativeDelayedTask(Runnable task, long delay) {
+ return false;
+ }
+
+ /**
* Runs a single task and returns when its finished.
*/
// The trace event name is derived from string literals.
diff --git a/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java b/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java
index 460bf1f04..95b57a895 100644
--- a/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java
+++ b/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java
@@ -6,8 +6,6 @@ package org.chromium.base.task;
import android.os.Handler;
-import org.chromium.base.ThreadUtils;
-
/**
* This {@link TaskExecutor} is for tasks posted with UI Thread {@link TaskTraits}. It maps to
* content::BrowserTaskExecutor in C++, except that in Java the UI thread is a base/ concept and
@@ -20,8 +18,7 @@ public class UiThreadTaskExecutor implements TaskExecutor {
private final SingleThreadTaskRunner mUserVisibleTaskRunner;
private final SingleThreadTaskRunner mUserBlockingTaskRunner;
- public UiThreadTaskExecutor() {
- Handler handler = ThreadUtils.getUiThreadHandler();
+ public UiThreadTaskExecutor(Handler handler) {
mBestEffortTaskRunner = new SingleThreadTaskRunnerImpl(handler, TaskTraits.UI_BEST_EFFORT);
mUserVisibleTaskRunner =
new SingleThreadTaskRunnerImpl(handler, TaskTraits.UI_USER_VISIBLE);
diff --git a/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java b/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java
new file mode 100644
index 000000000..eb369f61a
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java
@@ -0,0 +1,145 @@
+// Copyright 2023 The Chromium Authors
+// 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 static junit.framework.Assert.assertEquals;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ * Unit tests for {@link ResettersForTesting}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ResettersForTestingTest {
+ private static class ResetsToNull {
+ public static String str;
+ public static void setStrForTesting(String newStr) {
+ str = newStr;
+ ResettersForTesting.register(() -> str = null);
+ }
+ }
+
+ private static class ResetsToOldValue {
+ public static String str;
+
+ public static void setStrForTesting(String newStr) {
+ String oldValue = str;
+ str = newStr;
+ ResettersForTesting.register(() -> str = oldValue);
+ }
+ }
+
+ private static class ResetsToNullAndIncrements {
+ public static String str;
+ public static int resetCount;
+
+ public static void setStrForTesting(String newStr) {
+ str = newStr;
+ ResettersForTesting.register(() -> {
+ str = null;
+ resetCount++;
+ });
+ }
+ }
+
+ private static class ResetsToNullAndIncrementsWithOneShotResetter {
+ public static String str;
+ public static int resetCount;
+ private static Runnable sResetter = () -> {
+ str = null;
+ resetCount++;
+ };
+
+ public static void setStrForTesting(String newStr) {
+ str = newStr;
+ ResettersForTesting.register(sResetter);
+ }
+ }
+
+ @Test
+ public void testTypicalUsage() {
+ ResetsToNull.setStrForTesting("foo");
+ assertEquals("foo", ResetsToNull.str);
+ ResettersForTesting.executeResetters();
+ Assert.assertNull(ResetsToNull.str);
+ }
+
+ @Test
+ public void testResetsToPreviousValue() {
+ // Inject a previous value to verify that we can get back to it.
+ ResetsToOldValue.str = "bar";
+
+ ResetsToOldValue.setStrForTesting("foo");
+ assertEquals("foo", ResetsToOldValue.str);
+
+ // After resetting the value, it should be back to the first value.
+ ResettersForTesting.executeResetters();
+ assertEquals("bar", ResetsToOldValue.str);
+ }
+
+ @Test
+ public void testMultipleResets() {
+ // Inject an outer value to verify we can get back to this.
+ ResetsToOldValue.str = "qux";
+
+ // Then set the next value.
+ ResetsToOldValue.setStrForTesting("foo");
+ assertEquals("foo", ResetsToOldValue.str);
+
+ // Now, next that value into another one to ensure the unwinding works.
+ ResetsToOldValue.setStrForTesting("bar");
+ assertEquals("bar", ResetsToOldValue.str);
+
+ // Since we are invoking the resetters in the reverse order, we should now be back to start.
+ ResettersForTesting.executeResetters();
+ assertEquals("qux", ResetsToOldValue.str);
+ }
+
+ @Test
+ public void testResettersExecutedOnlyOnce() {
+ // Force set this to 0 for this particular test.
+ ResetsToNullAndIncrements.resetCount = 0;
+ ResetsToNullAndIncrements.str = null;
+
+ // Set the initial value and register the resetter.
+ ResetsToNullAndIncrements.setStrForTesting("some value");
+ assertEquals("some value", ResetsToNullAndIncrements.str);
+
+ // Now, execute all resetters and ensure it's only executed once.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrements.resetCount);
+
+ // Execute the resetters again, and verify it does not invoke the same resetter again.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrements.resetCount);
+ }
+
+ @Test
+ public void testResettersExecutedOnlyOnceForOneShotResetters() {
+ // Force set this to 0 for this particular test.
+ ResetsToNullAndIncrementsWithOneShotResetter.resetCount = 0;
+ ResetsToNullAndIncrementsWithOneShotResetter.str = null;
+
+ // Set the initial value and register the resetter twice.
+ ResetsToNullAndIncrementsWithOneShotResetter.setStrForTesting("some value");
+ ResetsToNullAndIncrementsWithOneShotResetter.setStrForTesting("some other value");
+ assertEquals("some other value", ResetsToNullAndIncrementsWithOneShotResetter.str);
+
+ // Now, execute all resetters and ensure it's only executed once, since it is a single
+ // instance of the same resetter.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrementsWithOneShotResetter.resetCount);
+
+ // Execute the resetters again, and verify it does not invoke the same resetter again.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrementsWithOneShotResetter.resetCount);
+ }
+}
diff --git a/base/android/proguard/chromium_code.flags b/base/android/proguard/chromium_code.flags
index 5f5bb55df..b8ee9ea74 100644
--- a/base/android/proguard/chromium_code.flags
+++ b/base/android/proguard/chromium_code.flags
@@ -13,12 +13,8 @@
@androidx.annotation.Keep <methods>;
}
-# Even unused methods kept due to explicit jni registration:
-# https://crbug.com/688465.
--keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class !org.chromium.base.library_loader.**,** {
- native <methods>;
-}
--keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class org.chromium.base.library_loader.** {
+# Allow unused native methods to be removed, but prevent renaming on those that are kept.
+-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class ** {
native <methods>;
}
diff --git a/base/android/proguard/remove_logging.flags b/base/android/proguard/remove_logging.flags
index 2e01eea2e..3bc707c28 100644
--- a/base/android/proguard/remove_logging.flags
+++ b/base/android/proguard/remove_logging.flags
@@ -2,17 +2,10 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-
-# Remove Log.d(), Log.v(), and all isLoggable() where the return value is
-# unused (e.g. if it was guarding a call to Log.d()).
--assumenosideeffects class android.util.Log {
- static int d(...);
- static int v(...);
- # Don't use "return false" here because some code guards info logs behind
- # isLoggable(DEBUG) as a way to enable logs via adb.
- # https://crbug.com/1366880
- static boolean isLoggable(...);
-}
+# Remove Log.d(), Log.v(), and corresponding isLoggable() calls.
+# Log.DEBUG = 3, Log.VERBOSE = 2.
+# https://stackoverflow.com/questions/73876633/what-does-the-r8-maximumremovedandroidloglevel-option
+-maximumremovedandroidloglevel 3
# Makes try-with-resources less inefficient. Saved 3.8kb when added.
-assumenosideeffects class java.lang.Throwable {
diff --git a/base/check_op.h b/base/check_op.h
index 07ad07d1e..d323229fc 100644
--- a/base/check_op.h
+++ b/base/check_op.h
@@ -15,6 +15,7 @@
#include "base/debug/debugging_buildflags.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/template_util.h"
+#include "base/types/supports_ostream_operator.h"
// This header defines the (DP)CHECK_EQ etc. macros.
//
diff --git a/base/containers/span.h b/base/containers/span.h
index 65b392b7a..ce3e01727 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -6,6 +6,7 @@
#define BASE_CONTAINERS_SPAN_H_
#include <stddef.h>
+#include <stdint.h>
#include <array>
#include <iterator>
diff --git a/base/critical_closure.h b/base/critical_closure.h
index d2673de35..7e3369ea8 100644
--- a/base/critical_closure.h
+++ b/base/critical_closure.h
@@ -82,6 +82,10 @@ class PendingCriticalClosure {
inline OnceClosure MakeCriticalClosure(StringPiece task_name,
OnceClosure closure,
bool is_immediate) {
+ // Wrapping a null closure in a critical closure has unclear semantics and
+ // most likely indicates a bug. CHECK-ing early allows detecting and
+ // investigating these cases more easily.
+ CHECK(!closure.is_null());
if (is_immediate) {
return base::BindOnce(&internal::ImmediateCriticalClosure::Run,
Owned(new internal::ImmediateCriticalClosure(
diff --git a/base/critical_closure_internal_ios.mm b/base/critical_closure_internal_ios.mm
index 736caca89..859a3a636 100644
--- a/base/critical_closure_internal_ios.mm
+++ b/base/critical_closure_internal_ios.mm
@@ -11,21 +11,27 @@ namespace internal {
ImmediateCriticalClosure::ImmediateCriticalClosure(StringPiece task_name,
OnceClosure closure)
- : critical_action_(task_name), closure_(std::move(closure)) {}
+ : critical_action_(task_name), closure_(std::move(closure)) {
+ CHECK(!closure_.is_null());
+}
ImmediateCriticalClosure::~ImmediateCriticalClosure() {}
void ImmediateCriticalClosure::Run() {
+ CHECK(!closure_.is_null());
std::move(closure_).Run();
}
PendingCriticalClosure::PendingCriticalClosure(StringPiece task_name,
OnceClosure closure)
- : task_name_(task_name), closure_(std::move(closure)) {}
+ : task_name_(task_name), closure_(std::move(closure)) {
+ CHECK(!closure_.is_null());
+}
PendingCriticalClosure::~PendingCriticalClosure() {}
void PendingCriticalClosure::Run() {
+ CHECK(!closure_.is_null());
critical_action_.emplace(task_name_);
std::move(closure_).Run();
}
diff --git a/base/debug/allocation_trace_perftest.cc b/base/debug/allocation_trace_perftest.cc
new file mode 100644
index 000000000..78e348f5e
--- /dev/null
+++ b/base/debug/allocation_trace_perftest.cc
@@ -0,0 +1,211 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <thread>
+#include <vector>
+
+#include "base/debug/allocation_trace.h"
+#include "base/strings/stringprintf.h"
+#include "base/timer/lap_timer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+
+namespace base {
+namespace debug {
+namespace {
+// Change kTimeLimit to something higher if you need more time to capture a
+// trace.
+constexpr base::TimeDelta kTimeLimit = base::Seconds(3);
+constexpr int kWarmupRuns = 100;
+constexpr int kTimeCheckInterval = 1000;
+constexpr char kMetricStackTraceDuration[] = ".duration_per_run";
+constexpr char kMetricStackTraceThroughput[] = ".throughput";
+
+enum class HandlerFunctionSelector { OnAllocation, OnFree };
+
+// An executor to perform the actual notification of the recorder. The correct
+// handler function is selected using template specialization based on the
+// HandlerFunctionSelector.
+template <HandlerFunctionSelector HandlerFunction>
+struct HandlerFunctionExecutor {
+ void operator()(base::debug::tracer::AllocationTraceRecorder& recorder) const;
+};
+
+template <>
+struct HandlerFunctionExecutor<HandlerFunctionSelector::OnAllocation> {
+ void operator()(
+ base::debug::tracer::AllocationTraceRecorder& recorder) const {
+ // Since the recorder just stores the value, we can use any value for
+ // address and size that we want.
+ recorder.OnAllocation(
+ &recorder, sizeof(recorder),
+ base::allocator::dispatcher::AllocationSubsystem::kPartitionAllocator,
+ nullptr);
+ }
+};
+
+template <>
+struct HandlerFunctionExecutor<HandlerFunctionSelector::OnFree> {
+ void operator()(
+ base::debug::tracer::AllocationTraceRecorder& recorder) const {
+ recorder.OnFree(&recorder);
+ }
+};
+} // namespace
+
+class AllocationTraceRecorderPerfTest
+ : public testing::TestWithParam<
+ std::tuple<HandlerFunctionSelector, size_t>> {
+ protected:
+ // The result data of a single thread. From the results of all the single
+ // threads the final results will be calculated.
+ struct ResultData {
+ TimeDelta time_per_lap;
+ float laps_per_second = 0.0;
+ int number_of_laps = 0;
+ };
+
+ // The data of a single test thread.
+ struct ThreadRunnerData {
+ std::thread thread;
+ ResultData result_data;
+ };
+
+ // Create and setup the result reporter.
+ const char* GetHandlerDescriptor(HandlerFunctionSelector handler_function);
+ perf_test::PerfResultReporter SetUpReporter(
+ HandlerFunctionSelector handler_function,
+ size_t number_of_allocating_threads);
+
+ // Select the correct test function which shall be used for the current test.
+ using TestFunction =
+ void (*)(base::debug::tracer::AllocationTraceRecorder& recorder,
+ ResultData& result_data);
+
+ static TestFunction GetTestFunction(HandlerFunctionSelector handler_function);
+ template <HandlerFunctionSelector HandlerFunction>
+ static void TestFunctionImplementation(
+ base::debug::tracer::AllocationTraceRecorder& recorder,
+ ResultData& result_data);
+
+ // The test management function. Using the the above auxiliary functions it is
+ // responsible to setup the result reporter, select the correct test function,
+ // spawn the specified number of worker threads and post process the results.
+ void PerformTest(HandlerFunctionSelector handler_function,
+ size_t number_of_allocating_threads);
+};
+
+const char* AllocationTraceRecorderPerfTest::GetHandlerDescriptor(
+ HandlerFunctionSelector handler_function) {
+ switch (handler_function) {
+ case HandlerFunctionSelector::OnAllocation:
+ return "OnAllocation";
+ case HandlerFunctionSelector::OnFree:
+ return "OnFree";
+ }
+}
+
+perf_test::PerfResultReporter AllocationTraceRecorderPerfTest::SetUpReporter(
+ HandlerFunctionSelector handler_function,
+ size_t number_of_allocating_threads) {
+ const std::string story_name = base::StringPrintf(
+ "(%s;%zu-threads)", GetHandlerDescriptor(handler_function),
+ number_of_allocating_threads);
+
+ perf_test::PerfResultReporter reporter("AllocationRecorderPerf", story_name);
+ reporter.RegisterImportantMetric(kMetricStackTraceDuration, "ns");
+ reporter.RegisterImportantMetric(kMetricStackTraceThroughput, "runs/s");
+ return reporter;
+}
+
+AllocationTraceRecorderPerfTest::TestFunction
+AllocationTraceRecorderPerfTest::GetTestFunction(
+ HandlerFunctionSelector handler_function) {
+ switch (handler_function) {
+ case HandlerFunctionSelector::OnAllocation:
+ return TestFunctionImplementation<HandlerFunctionSelector::OnAllocation>;
+ case HandlerFunctionSelector::OnFree:
+ return TestFunctionImplementation<HandlerFunctionSelector::OnFree>;
+ }
+}
+
+void AllocationTraceRecorderPerfTest::PerformTest(
+ HandlerFunctionSelector handler_function,
+ size_t number_of_allocating_threads) {
+ perf_test::PerfResultReporter reporter =
+ SetUpReporter(handler_function, number_of_allocating_threads);
+
+ TestFunction test_function = GetTestFunction(handler_function);
+
+ base::debug::tracer::AllocationTraceRecorder the_recorder;
+
+ std::vector<ThreadRunnerData> notifying_threads;
+ notifying_threads.reserve(number_of_allocating_threads);
+
+ // Setup the threads. After creation, each thread immediately starts running.
+ // We expect the creation of the threads to be so quick that the delay from
+ // first to last thread is negligible.
+ for (size_t i = 0; i < number_of_allocating_threads; ++i) {
+ auto& last_item = notifying_threads.emplace_back();
+
+ last_item.thread = std::thread{test_function, std::ref(the_recorder),
+ std::ref(last_item.result_data)};
+ }
+
+ TimeDelta average_time_per_lap;
+ float average_laps_per_second = 0;
+
+ // Wait for each thread to finish and collect its result data.
+ for (auto& item : notifying_threads) {
+ item.thread.join();
+ // When finishing, each threads writes its results into result_data. So,
+ // from here we gather its performance statistics.
+ average_time_per_lap += item.result_data.time_per_lap;
+ average_laps_per_second += item.result_data.laps_per_second;
+ }
+
+ average_time_per_lap /= number_of_allocating_threads;
+ average_laps_per_second /= number_of_allocating_threads;
+
+ reporter.AddResult(kMetricStackTraceDuration, average_time_per_lap);
+ reporter.AddResult(kMetricStackTraceThroughput, average_laps_per_second);
+}
+
+template <HandlerFunctionSelector HandlerFunction>
+void AllocationTraceRecorderPerfTest::TestFunctionImplementation(
+ base::debug::tracer::AllocationTraceRecorder& recorder,
+ ResultData& result_data) {
+ LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval,
+ LapTimer::TimerMethod::kUseTimeTicks);
+
+ HandlerFunctionExecutor<HandlerFunction> handler_executor;
+
+ timer.Start();
+ do {
+ handler_executor(recorder);
+
+ timer.NextLap();
+ } while (!timer.HasTimeLimitExpired());
+
+ result_data.time_per_lap = timer.TimePerLap();
+ result_data.laps_per_second = timer.LapsPerSecond();
+ result_data.number_of_laps = timer.NumLaps();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ,
+ AllocationTraceRecorderPerfTest,
+ ::testing::Combine(::testing::Values(HandlerFunctionSelector::OnAllocation,
+ HandlerFunctionSelector::OnFree),
+ ::testing::Values(1, 5, 10, 20, 40, 80)));
+
+TEST_P(AllocationTraceRecorderPerfTest, TestNotification) {
+ const auto parameters = GetParam();
+ const HandlerFunctionSelector handler_function = std::get<0>(parameters);
+ const size_t number_of_threads = std::get<1>(parameters);
+ PerformTest(handler_function, number_of_threads);
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/debug.gni b/base/debug/debug.gni
index f13f156b5..848d61a2a 100644
--- a/base/debug/debug.gni
+++ b/base/debug/debug.gni
@@ -14,7 +14,7 @@ declare_args() {
#
# Although it should work on other platforms as well, for the above reasons,
# we currently enable it only for Android when compiling for Arm64.
- build_allocation_stack_trace_recorder = current_cpu == "arm64" && is_android
+ build_allocation_stack_trace_recorder = false
}
assert(!(build_allocation_stack_trace_recorder && is_fuchsia),
diff --git a/base/debug/dump_without_crashing.h b/base/debug/dump_without_crashing.h
index d5d014d5a..d3fc807a7 100644
--- a/base/debug/dump_without_crashing.h
+++ b/base/debug/dump_without_crashing.h
@@ -65,8 +65,11 @@ NOT_TAIL_CALLED BASE_EXPORT bool DumpWithoutCrashing(
// using base::FastHash to generate the hash.
// `location` Location of the file from where the function is called.
// `time_between_dumps` Time until the next dump should be captured.
-// Note: The unique identifier, as of now, is not comparable across different
-// runs or builds and is stable only for a process lifetime.
+// Note:
+// - The unique identifier, as of now, is not comparable across different
+// runs or builds and is stable only for a process lifetime.
+// - The unique identifier is not recorded in the crash report. See
+// crash_logging.h for such a purpose.
NOT_TAIL_CALLED BASE_EXPORT bool DumpWithoutCrashingWithUniqueId(
size_t unique_identifier,
const base::Location& location = base::Location::Current(),
diff --git a/base/features.cc b/base/features.cc
index a94eadb8f..48f240113 100644
--- a/base/features.cc
+++ b/base/features.cc
@@ -22,4 +22,19 @@ BASE_FEATURE(kSupportsUserDataFlatHashMap,
"SupportsUserDataFlatHashMap",
FEATURE_DISABLED_BY_DEFAULT);
+#if BUILDFLAG(IS_ANDROID)
+// Force to enable LowEndDeviceMode partially on Android mid-range devices.
+// Such devices aren't considered low-end, but we'd like experiment with
+// a subset of low-end features to see if we get a good memory vs. performance
+// tradeoff.
+//
+// TODO(crbug.com/1434873): |#if| out 32-bit before launching or going to
+// high Stable %, because we will enable the feature only for <8GB 64-bit
+// devices, where we didn't ship yet. However, we first need a larger
+// population to collect data.
+BASE_FEATURE(kPartialLowEndModeOnMidRangeDevices,
+ "PartialLowEndModeOnMidRangeDevices",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+#endif // BUILDFLAG(IS_ANDROID)
+
} // namespace base::features
diff --git a/base/features.h b/base/features.h
index c61e07c0a..ccd15327f 100644
--- a/base/features.h
+++ b/base/features.h
@@ -20,6 +20,10 @@ BASE_EXPORT BASE_DECLARE_FEATURE(kOptimizeDataUrls);
BASE_EXPORT BASE_DECLARE_FEATURE(kSupportsUserDataFlatHashMap);
+#if BUILDFLAG(IS_ANDROID)
+BASE_EXPORT BASE_DECLARE_FEATURE(kPartialLowEndModeOnMidRangeDevices);
+#endif
+
} // namespace base::features
#endif // BASE_FEATURES_H_
diff --git a/base/files/file_path.cc b/base/files/file_path.cc
index 12c684bed..59bbc6e15 100644
--- a/base/files/file_path.cc
+++ b/base/files/file_path.cc
@@ -789,7 +789,7 @@ int FilePath::CompareIgnoreCase(StringPieceType string1,
#elif BUILDFLAG(IS_APPLE)
// Mac OS X specific implementation of file string comparisons.
-// cf. http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties
+// cf. https://developer.apple.com/library/archive/technotes/tn/tn1150.html#UnicodeSubtleties
//
// "When using CreateTextEncoding to create a text encoding, you should set
// the TextEncodingBase to kTextEncodingUnicodeV2_0, set the
@@ -815,11 +815,12 @@ int FilePath::CompareIgnoreCase(StringPieceType string1,
// Ignored characters are mapped to zero.
//
// cf. downloadable file linked in
-// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm
+// https://developer.apple.com/library/archive/technotes/tn/tn1150.html#Downloads
namespace {
-const UInt16 lower_case_table[] = {
+// clang-format off
+const UInt16 lower_case_table[11 * 256] = {
// High-byte indices ( == 0 iff no case mapping and no ignorables )
/* 0 */ 0x0100, 0x0200, 0x0000, 0x0300, 0x0400, 0x0500, 0x0000, 0x0000,
@@ -1205,11 +1206,12 @@ const UInt16 lower_case_table[] = {
/* F */ 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7,
0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF,
};
+// clang-format on
-// Returns the next non-ignorable codepoint within string starting from the
-// position indicated by index, or zero if there are no more.
-// The passed-in index is automatically advanced as the characters in the input
-// HFS-decomposed UTF-8 strings are read.
+// Returns the next non-ignorable codepoint within `string` starting from the
+// position indicated by `index`, or zero if there are no more.
+// The passed-in `index` is automatically advanced as the characters in the
+// input HFS-decomposed UTF-8 strings are read.
inline base_icu::UChar32 HFSReadNextNonIgnorableCodepoint(const char* string,
size_t length,
size_t* index) {
@@ -1220,12 +1222,16 @@ inline base_icu::UChar32 HFSReadNextNonIgnorableCodepoint(const char* string,
CBU8_NEXT(reinterpret_cast<const uint8_t*>(string), *index, length,
codepoint);
DCHECK_GT(codepoint, 0);
- if (codepoint > 0) {
+
+ // Note: Here, there are no lower case conversion implemented in the
+ // Supplementary Multilingual Plane (codepoint > 0xFFFF).
+
+ if (codepoint > 0 && codepoint <= 0xFFFF) {
// Check if there is a subtable for this upper byte.
int lookup_offset = lower_case_table[codepoint >> 8];
if (lookup_offset != 0)
codepoint = lower_case_table[lookup_offset + (codepoint & 0x00FF)];
- // Note: codepoint1 may be again 0 at this point if the character was
+ // Note: `codepoint` may be again 0 at this point if the character was
// an ignorable.
}
}
diff --git a/base/files/file_path_unittest.cc b/base/files/file_path_unittest.cc
index 7b30b15b8..9673a48bc 100644
--- a/base/files/file_path_unittest.cc
+++ b/base/files/file_path_unittest.cc
@@ -1203,6 +1203,13 @@ TEST_F(FilePathTest, CompareIgnoreCase) {
{{FPL("K\u0301U\u032DO\u0304\u0301N"), FPL("\u1E31\u1E77\u1E53n")}, 0},
{{FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("\u1E30\u1E76\u1E52n")}, 0},
{{FPL("k\u0301u\u032Do\u0304\u0302n"), FPL("\u1E30\u1E76\u1E52n")}, 1},
+
+ // Codepoints > 0xFFFF
+ // Here, we compare the `Adlam Letter Shu` in its capital and small version.
+ {{FPL("\U0001E921"), FPL("\U0001E943")}, -1},
+ {{FPL("\U0001E943"), FPL("\U0001E921")}, 1},
+ {{FPL("\U0001E921"), FPL("\U0001E921")}, 0},
+ {{FPL("\U0001E943"), FPL("\U0001E943")}, 0},
#endif
};
diff --git a/base/files/file_util.h b/base/files/file_util.h
index 11fa046db..0b0c7df15 100644
--- a/base/files/file_util.h
+++ b/base/files/file_util.h
@@ -272,6 +272,8 @@ BASE_EXPORT bool ReadFromFD(int fd, char* buffer, size_t bytes);
// Performs the same function as CreateAndOpenTemporaryStreamInDir(), but
// returns the file-descriptor wrapped in a ScopedFD, rather than the stream
// wrapped in a ScopedFILE.
+// The caller is responsible for deleting the file `path` points to, if
+// appropriate.
BASE_EXPORT ScopedFD CreateAndOpenFdForTemporaryFileInDir(const FilePath& dir,
FilePath* path);
@@ -370,18 +372,25 @@ BASE_EXPORT bool GetTempDir(FilePath* path);
BASE_EXPORT FilePath GetHomeDir();
// Returns a new temporary file in |dir| with a unique name. The file is opened
-// for exclusive read, write, and delete access (note: exclusivity is unique to
-// Windows). On Windows, the returned file supports File::DeleteOnClose.
+// for exclusive read, write, and delete access.
// On success, |temp_file| is populated with the full path to the created file.
+//
+// NOTE: Exclusivity is unique to Windows. On Windows, the returned file
+// supports File::DeleteOnClose. On other platforms, the caller is responsible
+// for deleting the file `temp_file` points to, if appropriate.
BASE_EXPORT File CreateAndOpenTemporaryFileInDir(const FilePath& dir,
FilePath* temp_file);
-// Creates a temporary file. The full path is placed in |path|, and the
+// Creates a temporary file. The full path is placed in `path`, and the
// function returns true if was successful in creating the file. The file will
// be empty and all handles closed after this function returns.
+// The caller is responsible for deleting the file `path` points to, if
+// appropriate.
BASE_EXPORT bool CreateTemporaryFile(FilePath* path);
-// Same as CreateTemporaryFile but the file is created in |dir|.
+// Same as CreateTemporaryFile() but the file is created in `dir`.
+// The caller is responsible for deleting the file `temp_file` points to, if
+// appropriate.
BASE_EXPORT bool CreateTemporaryFileInDir(const FilePath& dir,
FilePath* temp_file);
@@ -391,11 +400,14 @@ BASE_EXPORT FilePath
FormatTemporaryFileName(FilePath::StringPieceType identifier);
// Create and open a temporary file stream for exclusive read, write, and delete
-// access (note: exclusivity is unique to Windows). The full path is placed in
-// |path|. Returns the opened file stream, or null in case of error.
+// access. The full path is placed in `path`. Returns the opened file stream, or
+// null in case of error.
+// NOTE: Exclusivity is unique to Windows. On Windows, the returned file
+// supports File::DeleteOnClose. On other platforms, the caller is responsible
+// for deleting the file `path` points to, if appropriate.
BASE_EXPORT ScopedFILE CreateAndOpenTemporaryStream(FilePath* path);
-// Similar to CreateAndOpenTemporaryStream, but the file is created in |dir|.
+// Similar to CreateAndOpenTemporaryStream(), but the file is created in `dir`.
BASE_EXPORT ScopedFILE CreateAndOpenTemporaryStreamInDir(const FilePath& dir,
FilePath* path);
diff --git a/base/fuchsia/startup_context.cc b/base/fuchsia/startup_context.cc
index e93634c29..78210c552 100644
--- a/base/fuchsia/startup_context.cc
+++ b/base/fuchsia/startup_context.cc
@@ -49,40 +49,6 @@ StartupContext::StartupContext(
StartupContext::~StartupContext() = default;
-StartupContext::StartupContext(fuchsia::sys::StartupInfo startup_info) {
- std::unique_ptr<sys::ServiceDirectory> incoming_services;
-
- // Component manager generates |flat_namespace|, so things are horribly broken
- // if |flat_namespace| is malformed.
- CHECK_EQ(startup_info.flat_namespace.directories.size(),
- startup_info.flat_namespace.paths.size());
-
- // Find the /svc directory and wrap it into a sys::ServiceDirectory.
- for (size_t i = 0; i < startup_info.flat_namespace.paths.size(); ++i) {
- if (startup_info.flat_namespace.paths[i] == kServiceDirectoryPath) {
- incoming_services = std::make_unique<sys::ServiceDirectory>(
- std::move(startup_info.flat_namespace.directories[i]));
- break;
- }
- }
-
- if (!incoming_services) {
- LOG(WARNING) << "Component started without a service directory";
-
- // Create a dummy ServiceDirectory with a channel that's not
- // connected on the other end.
- fidl::InterfaceHandle<fuchsia::io::Directory> dummy_directory;
- std::ignore = dummy_directory.NewRequest();
- incoming_services =
- std::make_unique<sys::ServiceDirectory>(std::move(dummy_directory));
- }
-
- component_context_ =
- std::make_unique<sys::ComponentContext>(std::move(incoming_services));
- outgoing_directory_request_ =
- std::move(startup_info.launch_info.directory_request);
-}
-
void StartupContext::ServeOutgoingDirectory() {
DCHECK(outgoing_directory_request_);
component_context_->outgoing()->Serve(std::move(outgoing_directory_request_));
diff --git a/base/fuchsia/startup_context.h b/base/fuchsia/startup_context.h
index 44c7236de..fbe3f25cf 100644
--- a/base/fuchsia/startup_context.h
+++ b/base/fuchsia/startup_context.h
@@ -7,7 +7,6 @@
#include <fuchsia/component/runner/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
-#include <fuchsia/sys/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/zx/channel.h>
@@ -22,7 +21,7 @@ class OutgoingDirectory;
namespace base {
-// Helper for unpacking a fuchsia.sys.StartupInfo and creating convenience
+// Helper for unpacking component start info and creating convenience
// wrappers for the various fields (e.g. the incoming & outgoing service
// directories, resolve launch URL etc).
// Embedders may derived from StartupContext to e.g. add bound pointers to
@@ -33,10 +32,6 @@ class BASE_EXPORT StartupContext final {
::fuchsia::component::runner::ComponentStartInfo start_info);
~StartupContext();
- // TODO(https://crbug.com/1065707): Remove this overload once the CFv1
- // Runner implementations are removed.
- explicit StartupContext(::fuchsia::sys::StartupInfo startup_info);
-
StartupContext(const StartupContext&) = delete;
StartupContext& operator=(const StartupContext&) = delete;
diff --git a/base/functional/callback.h b/base/functional/callback.h
index 189259f2d..773662ed3 100644
--- a/base/functional/callback.h
+++ b/base/functional/callback.h
@@ -165,7 +165,7 @@ class TRIVIAL_ABI OnceCallback<R(Args...)> {
template <typename ThenR, typename... ThenArgs>
OnceCallback<ThenR(Args...)> Then(OnceCallback<ThenR(ThenArgs...)> then) && {
CHECK(then);
- return BindOnce(
+ return base::BindOnce(
internal::ThenHelper<
OnceCallback, OnceCallback<ThenR(ThenArgs...)>>::CreateTrampoline(),
std::move(*this), std::move(then));
@@ -178,7 +178,7 @@ class TRIVIAL_ABI OnceCallback<R(Args...)> {
OnceCallback<ThenR(Args...)> Then(
RepeatingCallback<ThenR(ThenArgs...)> then) && {
CHECK(then);
- return BindOnce(
+ return base::BindOnce(
internal::ThenHelper<
OnceCallback,
RepeatingCallback<ThenR(ThenArgs...)>>::CreateTrampoline(),
diff --git a/base/ios/scoped_critical_action.mm b/base/ios/scoped_critical_action.mm
index 038da26ab..5e032d0bf 100644
--- a/base/ios/scoped_critical_action.mm
+++ b/base/ios/scoped_critical_action.mm
@@ -24,7 +24,7 @@ namespace ios {
BASE_FEATURE(kScopedCriticalActionReuseEnabled,
"ScopedCriticalActionReuseEnabled",
- base::FEATURE_DISABLED_BY_DEFAULT);
+ base::FEATURE_ENABLED_BY_DEFAULT);
namespace {
diff --git a/base/ios/sim_header_shims.h b/base/ios/sim_header_shims.h
index 1d30a7137..3f9f5e060 100644
--- a/base/ios/sim_header_shims.h
+++ b/base/ios/sim_header_shims.h
@@ -16,7 +16,8 @@
#include <sys/param.h>
// This file includes the necessary headers that are not part of the
-// iOS public SDK in order to support multiprocess support on iOS.
+// iOS public SDK in order to support multiprocess and memory instrumentations
+// on iOS.
__BEGIN_DECLS
@@ -40,7 +41,16 @@ const char* bootstrap_strerror(kern_return_t r);
#define BOOTSTRAP_NO_MEMORY 1105
#define BOOTSTRAP_NO_CHILDREN 1106
+// These values are copied from darwin-xnu/osfmk/mach/shared_region.h.
+// https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/osfmk/mach/shared_region.h#L86-L87
+#define SHARED_REGION_BASE_ARM64 0x180000000ULL
+#define SHARED_REGION_SIZE_ARM64 0x100000000ULL
+
int proc_pidpath(int pid, void* buffer, uint32_t buffersize);
+int proc_regionfilename(int pid,
+ uint64_t address,
+ void* buffer,
+ uint32_t buffersize);
#define PROC_PIDPATHINFO_MAXSIZE (4 * MAXPATHLEN)
__END_DECLS
diff --git a/base/mac/bundle_locations.h b/base/mac/bundle_locations.h
index 1aea08a9c..eb4986650 100644
--- a/base/mac/bundle_locations.h
+++ b/base/mac/bundle_locations.h
@@ -10,9 +10,6 @@
#if defined(__OBJC__)
#import <Foundation/Foundation.h>
-#else // __OBJC__
-class NSBundle;
-class NSURL;
#endif // __OBJC__
namespace base {
@@ -45,23 +42,26 @@ namespace base::mac {
// bundle is probably the one to use.
// Methods for retrieving the various bundles.
+BASE_EXPORT FilePath MainBundlePath();
+BASE_EXPORT FilePath OuterBundlePath();
+BASE_EXPORT FilePath FrameworkBundlePath();
+#if defined(__OBJC__)
BASE_EXPORT NSBundle* MainBundle();
BASE_EXPORT NSURL* MainBundleURL();
-BASE_EXPORT FilePath MainBundlePath();
BASE_EXPORT NSBundle* OuterBundle();
BASE_EXPORT NSURL* OuterBundleURL();
-BASE_EXPORT FilePath OuterBundlePath();
BASE_EXPORT NSBundle* FrameworkBundle();
-BASE_EXPORT FilePath FrameworkBundlePath();
+#endif // __OBJC__
// Set the bundle that the preceding functions will return, overriding the
-// default values. Restore the default by passing in |nil|.
-BASE_EXPORT void SetOverrideOuterBundle(NSBundle* bundle);
-BASE_EXPORT void SetOverrideFrameworkBundle(NSBundle* bundle);
-
-// Same as above but accepting a FilePath argument.
+// default values. Restore the default by passing in `nil` or an empty
+// `FilePath`.
BASE_EXPORT void SetOverrideOuterBundlePath(const FilePath& file_path);
BASE_EXPORT void SetOverrideFrameworkBundlePath(const FilePath& file_path);
+#if defined(__OBJC__)
+BASE_EXPORT void SetOverrideOuterBundle(NSBundle* bundle);
+BASE_EXPORT void SetOverrideFrameworkBundle(NSBundle* bundle);
+#endif // __OBJC__
} // namespace base::mac
diff --git a/base/mac/bundle_locations.mm b/base/mac/bundle_locations.mm
index 6835cadc2..111be3be2 100644
--- a/base/mac/bundle_locations.mm
+++ b/base/mac/bundle_locations.mm
@@ -61,9 +61,11 @@ static void AssignOverrideBundle(NSBundle* new_bundle,
static void AssignOverridePath(const FilePath& file_path,
NSBundle** override_bundle) {
- NSString* path = base::SysUTF8ToNSString(file_path.value());
- NSBundle* new_bundle = [NSBundle bundleWithPath:path];
- DCHECK(new_bundle) << "Failed to load the bundle at " << file_path.value();
+ NSBundle* new_bundle = nil;
+ if (!file_path.empty()) {
+ new_bundle = [NSBundle bundleWithURL:base::mac::FilePathToNSURL(file_path)];
+ CHECK(new_bundle) << "Failed to load the bundle at " << file_path.value();
+ }
AssignOverrideBundle(new_bundle, override_bundle);
}
diff --git a/base/mac/foundation_util.h b/base/mac/foundation_util.h
index cabf20207..159d1ffb8 100644
--- a/base/mac/foundation_util.h
+++ b/base/mac/foundation_util.h
@@ -7,6 +7,8 @@
#include <AvailabilityMacros.h>
#include <CoreFoundation/CoreFoundation.h>
+#include <CoreText/CoreText.h>
+#include <Security/Security.h>
#include <string>
@@ -19,33 +21,8 @@
#import <Foundation/Foundation.h>
@class NSFont;
@class UIFont;
-#else // __OBJC__
-class NSBundle;
-class NSFont;
-class NSString;
-class UIFont;
#endif // __OBJC__
-#if BUILDFLAG(IS_IOS)
-#include <CoreText/CoreText.h>
-#else
-#include <ApplicationServices/ApplicationServices.h>
-#endif
-
-// Adapted from NSPathUtilities.h and NSObjCRuntime.h.
-#if __LP64__ || NS_BUILD_32_LIKE_64
-enum NSSearchPathDirectory : unsigned long;
-typedef unsigned long NSSearchPathDomainMask;
-#else
-enum NSSearchPathDirectory : unsigned int;
-typedef unsigned int NSSearchPathDomainMask;
-#endif
-
-typedef struct CF_BRIDGED_TYPE(id) __SecAccessControl* SecAccessControlRef;
-typedef struct CF_BRIDGED_TYPE(id) __SecCertificate* SecCertificateRef;
-typedef struct CF_BRIDGED_TYPE(id) __SecKey* SecKeyRef;
-typedef struct CF_BRIDGED_TYPE(id) __SecPolicy* SecPolicyRef;
-
namespace base {
class FilePath;
}
@@ -82,6 +59,8 @@ OSType CreatorCodeForCFBundleRef(CFBundleRef bundle);
// app bundle's creator code anyway.
BASE_EXPORT OSType CreatorCodeForApplication();
+#if defined(__OBJC__)
+
// Searches for directories for the given key in only the given |domain_mask|.
// If found, fills result (which must always be non-NULL) with the
// first found directory and returns true. Otherwise, returns false.
@@ -101,6 +80,8 @@ BASE_EXPORT bool GetLocalDirectory(NSSearchPathDirectory directory,
BASE_EXPORT bool GetUserDirectory(NSSearchPathDirectory directory,
FilePath* result);
+#endif // __OBJC__
+
// Returns the ~/Library directory.
BASE_EXPORT FilePath GetUserLibraryPath();
@@ -157,11 +138,7 @@ BASE_EXPORT void SetBaseBundleID(const char* new_base_bundle_id);
// ARC. Use the casting functions in base/mac/bridging.h instead.
#if !defined(__has_feature) || !__has_feature(objc_arc)
-#if !defined(__OBJC__)
-#define OBJC_CPP_CLASS_DECL(x) class x;
-#else // __OBJC__
-#define OBJC_CPP_CLASS_DECL(x)
-#endif // __OBJC__
+#if defined(__OBJC__)
// Convert toll-free bridged CFTypes to NSTypes and vice-versa. This does not
// autorelease |cf_val|. This is useful for the case where there is a CFType in
@@ -176,26 +153,19 @@ BASE_EXPORT void SetBaseBundleID(const char* new_base_bundle_id);
// (http://www.gotw.ca/publications/mill17.htm) so the trusty combination
// of macros and function overloading is used instead.
-#define CF_TO_NS_CAST_DECL(TypeCF, TypeNS) \
-OBJC_CPP_CLASS_DECL(TypeNS) \
-\
-namespace base { \
-namespace mac { \
-BASE_EXPORT TypeNS* CFToNSCast(TypeCF##Ref cf_val); \
-BASE_EXPORT TypeCF##Ref NSToCFCast(TypeNS* ns_val); \
-} \
-}
+#define CF_TO_NS_CAST_DECL(TypeCF, TypeNS) \
+ namespace base::mac { \
+ BASE_EXPORT TypeNS* CFToNSCast(TypeCF##Ref cf_val); \
+ BASE_EXPORT TypeCF##Ref NSToCFCast(TypeNS* ns_val); \
+ }
-#define CF_TO_NS_MUTABLE_CAST_DECL(name) \
-CF_TO_NS_CAST_DECL(CF##name, NS##name) \
-OBJC_CPP_CLASS_DECL(NSMutable##name) \
-\
-namespace base { \
-namespace mac { \
-BASE_EXPORT NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val); \
-BASE_EXPORT CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val); \
-} \
-}
+#define CF_TO_NS_MUTABLE_CAST_DECL(name) \
+ CF_TO_NS_CAST_DECL(CF##name, NS##name) \
+ \
+ namespace base::mac { \
+ BASE_EXPORT NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val); \
+ BASE_EXPORT CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val); \
+ }
// List of toll-free bridged types taken from:
// http://www.cocoadev.com/index.pl?TollFreeBridged
@@ -228,6 +198,8 @@ CF_TO_NS_CAST_DECL(CTFont, NSFont)
#undef CF_TO_NS_MUTABLE_CAST_DECL
#undef OBJC_CPP_CLASS_DECL
+#endif // __OBJC__
+
#endif // !defined(__has_feature) || !__has_feature(objc_arc)
namespace base::mac {
@@ -350,6 +322,8 @@ T GetValueFromDictionary(CFDictionaryRef dict, CFStringRef key) {
return value_specific;
}
+#if defined(__OBJC__)
+
// Converts |path| to an autoreleased NSURL. Returns nil if |path| is empty.
BASE_EXPORT NSURL* FilePathToNSURL(const FilePath& path);
@@ -363,6 +337,8 @@ BASE_EXPORT FilePath NSStringToFilePath(NSString* str);
// |url| is not of scheme "file".
BASE_EXPORT FilePath NSURLToFilePath(NSURL* url);
+#endif // __OBJC__
+
// Converts a non-null |path| to a CFURLRef. |path| must not be empty.
//
// This function only uses manually-owned resources, so it does not depend on an
@@ -396,11 +372,12 @@ BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, id);
BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSRange);
BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, SEL);
-#if !BUILDFLAG(IS_IOS)
+#if BUILDFLAG(IS_MAC)
BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSPoint);
BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSRect);
BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSSize);
-#endif
-#endif
+#endif // IS_MAC
+
+#endif // __OBJC__
#endif // BASE_MAC_FOUNDATION_UTIL_H_
diff --git a/base/mac/scoped_nsautorelease_pool.h b/base/mac/scoped_nsautorelease_pool.h
index 1a824cc54..d556cd5f2 100644
--- a/base/mac/scoped_nsautorelease_pool.h
+++ b/base/mac/scoped_nsautorelease_pool.h
@@ -6,6 +6,7 @@
#define BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_
#include "base/base_export.h"
+#include "base/memory/raw_ptr_exclusion.h"
#include "base/threading/thread_checker.h"
namespace base::mac {
@@ -42,7 +43,9 @@ class BASE_EXPORT ScopedNSAutoreleasePool {
void Recycle();
private:
- void* autorelease_pool_ GUARDED_BY_CONTEXT(thread_checker_);
+ // This field is not a raw_ptr<> because it is a pointer to an Objective-C
+ // object.
+ RAW_PTR_EXCLUSION void* autorelease_pool_ GUARDED_BY_CONTEXT(thread_checker_);
THREAD_CHECKER(thread_checker_);
};
diff --git a/base/mac/scoped_nsobject.h b/base/mac/scoped_nsobject.h
index 710a46a6a..c8a26b223 100644
--- a/base/mac/scoped_nsobject.h
+++ b/base/mac/scoped_nsobject.h
@@ -5,22 +5,36 @@
#ifndef BASE_MAC_SCOPED_NSOBJECT_H_
#define BASE_MAC_SCOPED_NSOBJECT_H_
-#include <type_traits>
-
// Include NSObject.h directly because Foundation.h pulls in many dependencies.
// (Approx 100k lines of code versus 1.5k for NSObject.h). scoped_nsobject gets
// singled out because it is most typically included from other header files.
#import <Foundation/NSObject.h>
+#include <type_traits>
+#include <utility>
+
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/mac/scoped_typeref.h"
-#if defined(__has_feature) && __has_feature(objc_arc)
-#error "Cannot include base/mac/scoped_nsobject.h in file built with ARC."
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+@class NSAutoreleasePool;
#endif
-@class NSAutoreleasePool;
+// Note that this uses the direct runtime interface to reference counting.
+// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support
+// This is so this can work when compiled for ARC. Annotations are used to lie
+// about the effects of these calls so that ARC will not insert any reference
+// count calls of its own and leave the maintenance of the reference count to
+// this class.
+
+extern "C" {
+id objc_retain(__unsafe_unretained id value)
+ __attribute__((ns_returns_not_retained));
+void objc_release(__unsafe_unretained id value);
+id objc_autorelease(__unsafe_unretained id value)
+ __attribute__((ns_returns_not_retained));
+}
namespace base {
@@ -42,14 +56,21 @@ namespace base {
// NSAutoreleasePool; for Objective-C(++) code use @autoreleasepool instead. We
// check for bad uses of scoped_nsobject and NSAutoreleasePool at compile time
// with a template specialization (see below).
+//
+// If Automatic Reference Counting (aka ARC) is enabled then the ownership
+// policy is not controllable by the user as ARC make it really difficult to
+// transfer ownership (the reference passed to scoped_nsobject constructor is
+// sunk by ARC, and a design that a function will maybe consume an argument
+// based on a different argument isn't expressible with attributes). Due to
+// that, the policy is always to |RETAIN| when using ARC.
namespace internal {
template <typename NST>
struct ScopedNSProtocolTraits {
static NST InvalidValue() { return nil; }
- static NST Retain(NST nst) { return [nst retain]; }
- static void Release(NST nst) { [nst release]; }
+ static NST Retain(__unsafe_unretained NST nst) { return objc_retain(nst); }
+ static void Release(__unsafe_unretained NST nst) { objc_release(nst); }
};
} // namespace internal
@@ -58,11 +79,53 @@ template <typename NST>
class scoped_nsprotocol
: public ScopedTypeRef<NST, internal::ScopedNSProtocolTraits<NST>> {
public:
- using ScopedTypeRef<NST,
- internal::ScopedNSProtocolTraits<NST>>::ScopedTypeRef;
+ using Traits = internal::ScopedNSProtocolTraits<NST>;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+ explicit constexpr scoped_nsprotocol(
+ NST object = Traits::InvalidValue(),
+ base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME)
+ : ScopedTypeRef<NST, Traits>(object, policy) {}
+#else
+ explicit constexpr scoped_nsprotocol(NST object = Traits::InvalidValue())
+ : ScopedTypeRef<NST, Traits>(object, base::scoped_policy::RETAIN) {}
+#endif
+
+ scoped_nsprotocol(const scoped_nsprotocol<NST>& that)
+ : ScopedTypeRef<NST, Traits>(that) {}
+
+ template <typename NSR>
+ explicit scoped_nsprotocol(const scoped_nsprotocol<NSR>& that_as_subclass)
+ : ScopedTypeRef<NST, Traits>(that_as_subclass) {}
+
+ scoped_nsprotocol(scoped_nsprotocol<NST>&& that)
+ : ScopedTypeRef<NST, Traits>(std::move(that)) {}
+
+ scoped_nsprotocol& operator=(const scoped_nsprotocol<NST>& that) {
+ ScopedTypeRef<NST, Traits>::operator=(that);
+ return *this;
+ }
+
+ void reset(const scoped_nsprotocol<NST>& that) {
+ ScopedTypeRef<NST, Traits>::reset(that);
+ }
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+ void reset(NST object = Traits::InvalidValue(),
+ base::scoped_policy::OwnershipPolicy policy =
+ base::scoped_policy::ASSUME) {
+ ScopedTypeRef<NST, Traits>::reset(object, policy);
+ }
+#else
+ void reset(NST object = Traits::InvalidValue()) {
+ ScopedTypeRef<NST, Traits>::reset(object, base::scoped_policy::RETAIN);
+ }
+#endif
// Shift reference to the autorelease pool to be released later.
- NST autorelease() { return [this->release() autorelease]; }
+ NST autorelease() __attribute__((ns_returns_not_retained)) {
+ return objc_autorelease(this->release());
+ }
};
// Free functions
@@ -84,17 +147,97 @@ bool operator!=(C p1, const scoped_nsprotocol<C>& p2) {
template <typename NST>
class scoped_nsobject : public scoped_nsprotocol<NST*> {
public:
- using scoped_nsprotocol<NST*>::scoped_nsprotocol;
+ using Traits = typename scoped_nsprotocol<NST*>::Traits;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+ explicit constexpr scoped_nsobject(
+ NST* object = Traits::InvalidValue(),
+ base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME)
+ : scoped_nsprotocol<NST*>(object, policy) {}
+#else
+ explicit constexpr scoped_nsobject(NST* object = Traits::InvalidValue())
+ : scoped_nsprotocol<NST*>(object) {}
+#endif
+ scoped_nsobject(const scoped_nsobject<NST>& that)
+ : scoped_nsprotocol<NST*>(that) {}
+
+ template <typename NSR>
+ explicit scoped_nsobject(const scoped_nsobject<NSR>& that_as_subclass)
+ : scoped_nsprotocol<NST*>(that_as_subclass) {}
+
+ scoped_nsobject(scoped_nsobject<NST>&& that)
+ : scoped_nsprotocol<NST*>(std::move(that)) {}
+
+ scoped_nsobject& operator=(const scoped_nsobject<NST>& that) {
+ scoped_nsprotocol<NST*>::operator=(that);
+ return *this;
+ }
+
+ void reset(const scoped_nsobject<NST>& that) {
+ scoped_nsprotocol<NST*>::reset(that);
+ }
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+ void reset(NST* object = Traits::InvalidValue(),
+ base::scoped_policy::OwnershipPolicy policy =
+ base::scoped_policy::ASSUME) {
+ scoped_nsprotocol<NST*>::reset(object, policy);
+ }
+#else
+ void reset(NST* object = Traits::InvalidValue()) {
+ scoped_nsprotocol<NST*>::reset(object);
+ }
+#endif
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
static_assert(std::is_same<NST, NSAutoreleasePool>::value == false,
"Use @autoreleasepool instead");
+#endif
};
// Specialization to make scoped_nsobject<id> work.
template<>
class scoped_nsobject<id> : public scoped_nsprotocol<id> {
public:
- using scoped_nsprotocol<id>::scoped_nsprotocol;
+ using Traits = typename scoped_nsprotocol<id>::Traits;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+ explicit constexpr scoped_nsobject(
+ id object = Traits::InvalidValue(),
+ base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME)
+ : scoped_nsprotocol<id>(object, policy) {}
+#else
+ explicit constexpr scoped_nsobject(id object = Traits::InvalidValue())
+ : scoped_nsprotocol<id>(object) {}
+#endif
+
+ scoped_nsobject(const scoped_nsobject<id>& that) = default;
+
+ template <typename NSR>
+ explicit scoped_nsobject(const scoped_nsobject<NSR>& that_as_subclass)
+ : scoped_nsprotocol<id>(that_as_subclass) {}
+
+ scoped_nsobject(scoped_nsobject<id>&& that)
+ : scoped_nsprotocol<id>(std::move(that)) {}
+
+ scoped_nsobject& operator=(const scoped_nsobject<id>& that) = default;
+
+ void reset(const scoped_nsobject<id>& that) {
+ scoped_nsprotocol<id>::reset(that);
+ }
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+ void reset(id object = Traits::InvalidValue(),
+ base::scoped_policy::OwnershipPolicy policy =
+ base::scoped_policy::ASSUME) {
+ scoped_nsprotocol<id>::reset(object, policy);
+ }
+#else
+ void reset(id object = Traits::InvalidValue()) {
+ scoped_nsprotocol<id>::reset(object);
+ }
+#endif
};
} // namespace base
diff --git a/base/mac/scoped_nsobject_unittest_arc.mm b/base/mac/scoped_nsobject_unittest_arc.mm
new file mode 100644
index 000000000..f5f0046f1
--- /dev/null
+++ b/base/mac/scoped_nsobject_unittest_arc.mm
@@ -0,0 +1,176 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#include <vector>
+
+#import "base/mac/scoped_nsobject.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// A note about these tests.
+//
+// There are a lot of "autoreleasepool" scopes. Why? `ScopedTypeRef<>::get()` is
+// a function returning an unretained Obj-C object, so ARC ends the call with
+// `objc_retainAutoreleaseReturnValue()` hoping to make a hand-off to the caller
+// using `objc_retainAutoreleasedReturnValue()` to maintain the reference count.
+// Unfortunately, a simple call to `get()` won't take the hand-off, so the
+// result is an extra retain and pending autorelease.
+//
+// For tests that aren't doing retain count testing, this doesn't matter. For
+// tests that are doing retain count testing, all calls to `get()` have to be
+// made within an autorelease pool to ensure that those pending autoreleases
+// don't mess up the count.
+//
+// For further reading:
+// https://www.mikeash.com/pyblog/friday-qa-2014-05-09-when-an-autorelease-isnt.html
+
+namespace {
+
+template <typename NST>
+CFIndex GetRetainCount(const base::scoped_nsobject<NST>& nst) {
+ @autoreleasepool {
+ // -1 to compensate for the `get()` call creating an extra retain and
+ // pending autorelease.
+ return CFGetRetainCount((__bridge CFTypeRef)nst.get()) - 1;
+ }
+}
+
+TEST(ScopedNSObjectTestARC, DefaultPolicyIsRetain) {
+ id __weak o;
+ @autoreleasepool {
+ base::scoped_nsprotocol<id> p([[NSObject alloc] init]);
+ o = p.get();
+ ASSERT_EQ(o, p.get());
+ }
+ ASSERT_EQ(o, nil);
+}
+
+TEST(ScopedNSObjectTestARC, ScopedNSObject) {
+ base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ @autoreleasepool {
+ EXPECT_TRUE(p1.get());
+ EXPECT_TRUE(p1.get());
+ }
+ EXPECT_EQ(1, GetRetainCount(p1));
+ EXPECT_EQ(1, GetRetainCount(p1));
+ base::scoped_nsobject<NSObject> p2(p1);
+ @autoreleasepool {
+ EXPECT_EQ(p1.get(), p2.get());
+ }
+ EXPECT_EQ(2, GetRetainCount(p1));
+ p2.reset();
+ EXPECT_EQ(nil, p2.get());
+ EXPECT_EQ(1, GetRetainCount(p1));
+ {
+ base::scoped_nsobject<NSObject> p3 = p1;
+ @autoreleasepool {
+ EXPECT_EQ(p1.get(), p3.get());
+ }
+ EXPECT_EQ(2, GetRetainCount(p1));
+ @autoreleasepool {
+ p3 = p1;
+ EXPECT_EQ(p1.get(), p3.get());
+ }
+ EXPECT_EQ(2, GetRetainCount(p1));
+ }
+ EXPECT_EQ(1, GetRetainCount(p1));
+ base::scoped_nsobject<NSObject> p4;
+ @autoreleasepool {
+ p4 = base::scoped_nsobject<NSObject>(p1.get());
+ }
+ EXPECT_EQ(2, GetRetainCount(p1));
+ @autoreleasepool {
+ EXPECT_TRUE(p1 == p1.get());
+ EXPECT_TRUE(p1 == p1);
+ EXPECT_FALSE(p1 != p1);
+ EXPECT_FALSE(p1 != p1.get());
+ }
+ base::scoped_nsobject<NSObject> p5([[NSObject alloc] init]);
+ @autoreleasepool {
+ EXPECT_TRUE(p1 != p5);
+ EXPECT_TRUE(p1 != p5.get());
+ EXPECT_FALSE(p1 == p5);
+ EXPECT_FALSE(p1 == p5.get());
+ }
+
+ base::scoped_nsobject<NSObject> p6 = p1;
+ EXPECT_EQ(3, GetRetainCount(p6));
+ @autoreleasepool {
+ p6.autorelease();
+ EXPECT_EQ(nil, p6.get());
+ }
+ EXPECT_EQ(2, GetRetainCount(p1));
+}
+
+TEST(ScopedNSObjectTestARC, ScopedNSObjectInContainer) {
+ base::scoped_nsobject<id> p([[NSObject alloc] init]);
+ @autoreleasepool {
+ EXPECT_TRUE(p.get());
+ }
+ EXPECT_EQ(1, GetRetainCount(p));
+ @autoreleasepool {
+ std::vector<base::scoped_nsobject<id>> objects;
+ objects.push_back(p);
+ EXPECT_EQ(2, GetRetainCount(p));
+ @autoreleasepool {
+ EXPECT_EQ(p.get(), objects[0].get());
+ }
+ objects.push_back(base::scoped_nsobject<id>([[NSObject alloc] init]));
+ @autoreleasepool {
+ EXPECT_TRUE(objects[1].get());
+ }
+ EXPECT_EQ(1, GetRetainCount(objects[1]));
+ }
+ EXPECT_EQ(1, GetRetainCount(p));
+}
+
+TEST(ScopedNSObjectTestARC, ScopedNSObjectFreeFunctions) {
+ base::scoped_nsobject<id> p1([[NSObject alloc] init]);
+ id o1 = p1.get();
+ EXPECT_TRUE(o1 == p1);
+ EXPECT_FALSE(o1 != p1);
+ base::scoped_nsobject<id> p2([[NSObject alloc] init]);
+ EXPECT_TRUE(o1 != p2);
+ EXPECT_FALSE(o1 == p2);
+ id o2 = p2.get();
+ swap(p1, p2);
+ EXPECT_EQ(o2, p1.get());
+ EXPECT_EQ(o1, p2.get());
+}
+
+TEST(ScopedNSObjectTestARC, ResetWithAnotherScopedNSObject) {
+ // This test uses __unsafe_unretained because it holds raw pointers to do
+ // comparisons of them.
+
+ base::scoped_nsobject<id> p1([[NSObject alloc] init]);
+ id __unsafe_unretained o1;
+ @autoreleasepool {
+ o1 = p1.get();
+ }
+
+ id __unsafe_unretained o2;
+ {
+ base::scoped_nsobject<id> p2([[NSObject alloc] init]);
+ @autoreleasepool {
+ o2 = p2.get();
+ }
+ p1.reset(p2);
+ EXPECT_EQ(2u, GetRetainCount(p1));
+ }
+
+ @autoreleasepool {
+ EXPECT_NE(o1, p1.get());
+ EXPECT_EQ(o2, p1.get());
+ EXPECT_NE(p1.get(), nil);
+ }
+
+ EXPECT_EQ(1u, GetRetainCount(p1));
+}
+
+} // namespace
diff --git a/base/mac/scoped_typeref.h b/base/mac/scoped_typeref.h
index ac72cfdc0..8e18d145d 100644
--- a/base/mac/scoped_typeref.h
+++ b/base/mac/scoped_typeref.h
@@ -50,7 +50,7 @@ struct ScopedTypeRefTraits;
template<typename T, typename Traits = ScopedTypeRefTraits<T>>
class ScopedTypeRef {
public:
- using element_type = T;
+ using element_type = __unsafe_unretained T;
explicit constexpr ScopedTypeRef(
element_type object = Traits::InvalidValue(),
@@ -118,9 +118,13 @@ class ScopedTypeRef {
return object_ != that.object_;
}
- operator element_type() const { return object_; }
+ operator element_type() const __attribute__((ns_returns_not_retained)) {
+ return object_;
+ }
- element_type get() const { return object_; }
+ element_type get() const __attribute__((ns_returns_not_retained)) {
+ return object_;
+ }
void swap(ScopedTypeRef& that) {
element_type temp = that.object_;
diff --git a/base/memory/madv_free_discardable_memory_posix.cc b/base/memory/madv_free_discardable_memory_posix.cc
index a19747e04..a72aee6cf 100644
--- a/base/memory/madv_free_discardable_memory_posix.cc
+++ b/base/memory/madv_free_discardable_memory_posix.cc
@@ -24,6 +24,10 @@
#include "base/tracing_buildflags.h"
#include "build/build_config.h"
+#if BUILDFLAG(IS_ANDROID)
+#include <sys/prctl.h>
+#endif
+
#if BUILDFLAG(ENABLE_BASE_TRACING)
#include "base/trace_event/memory_allocator_dump.h" // no-presubmit-check
#include "base/trace_event/memory_dump_manager.h" // no-presubmit-check
@@ -38,9 +42,16 @@ namespace {
constexpr intptr_t kPageMagicCookie = 1;
void* AllocatePages(size_t size_in_pages) {
- void* data = mmap(nullptr, size_in_pages * base::GetPageSize(),
- PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ const size_t length = size_in_pages * base::GetPageSize();
+ void* data = mmap(nullptr, length, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
PCHECK(data != MAP_FAILED);
+
+#if BUILDFLAG(IS_ANDROID)
+ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, data, length,
+ "madv-free-discardable");
+#endif
+
return data;
}
diff --git a/base/memory/rust_cfg_win_unittest.cc b/base/memory/rust_cfg_win_unittest.cc
index 2abfb2e82..020fde1fa 100644
--- a/base/memory/rust_cfg_win_unittest.cc
+++ b/base/memory/rust_cfg_win_unittest.cc
@@ -18,8 +18,9 @@ namespace {
TEST(RustCfgWin, CfgCatchesInvalidIndirectCall) {
base::LaunchOptions o;
o.start_hidden = true;
+ // From //build/rust/tests/test_control_flow_guard.
base::CommandLine cmd(base::FilePath(
- FILE_PATH_LITERAL(RUST_CFG_WIN_UNITTEST_PATH_TO_RUST_EXE)));
+ FILE_PATH_LITERAL("test_control_flow_guard.exe")));
base::Process proc = base::LaunchProcess(cmd, o);
int exit_code;
EXPECT_TRUE(proc.WaitForExit(&exit_code));
diff --git a/base/message_loop/fd_watch_controller_posix_unittest.cc b/base/message_loop/fd_watch_controller_posix_unittest.cc
index 766f39264..87fdb1652 100644
--- a/base/message_loop/fd_watch_controller_posix_unittest.cc
+++ b/base/message_loop/fd_watch_controller_posix_unittest.cc
@@ -131,10 +131,10 @@ class CallClosureHandler : public MessagePumpForIO::FdWatcher {
TEST_F(FdWatchControllerPosixTest, FileDescriptorWatcherOutlivesMessageLoop) {
// Simulate a MessageLoop that dies before an FileDescriptorWatcher.
// This could happen when people use the Singleton pattern or atexit.
+ TestHandler handler;
// Arrange for watcher to live longer than message loop.
MessagePumpForIO::FdWatchController watcher(FROM_HERE);
- TestHandler handler;
{
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
@@ -329,10 +329,10 @@ TEST_P(MessageLoopForIoPosixReadAndWriteTest, AfterWrite) {
// Verify that basic readable notification works.
TEST_F(FdWatchControllerPosixTest, WatchReadable) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
TestHandler handler;
// Watch the pipe for readability.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor(
read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
&watcher, &handler));
@@ -355,10 +355,10 @@ TEST_F(FdWatchControllerPosixTest, WatchReadable) {
// Verify that watching a file descriptor for writability succeeds.
TEST_F(FdWatchControllerPosixTest, WatchWritable) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
TestHandler handler;
// Watch the pipe for writability.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor(
write_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_WRITE,
&watcher, &handler));
@@ -378,10 +378,10 @@ TEST_F(FdWatchControllerPosixTest, WatchWritable) {
// Verify that RunUntilIdle() receives IO notifications.
TEST_F(FdWatchControllerPosixTest, RunUntilIdle) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
TestHandler handler;
// Watch the pipe for readability.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor(
read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
&watcher, &handler));
@@ -426,12 +426,12 @@ TEST_F(FdWatchControllerPosixTest, StopFromHandler) {
// Verify that non-persistent watcher is called only once.
TEST_F(FdWatchControllerPosixTest, NonPersistentWatcher) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
RunLoop run_loop;
CallClosureHandler handler(run_loop.QuitClosure(), OnceClosure());
// Create a non-persistent watcher.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor(
read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
&watcher, &handler));
@@ -447,12 +447,12 @@ TEST_F(FdWatchControllerPosixTest, NonPersistentWatcher) {
// Verify that persistent watcher is called every time the event is triggered.
TEST_F(FdWatchControllerPosixTest, PersistentWatcher) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
RunLoop run_loop1;
CallClosureHandler handler(run_loop1.QuitClosure(), OnceClosure());
// Create persistent watcher.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor(
read_fd_.get(), /*persistent=*/true, MessagePumpForIO::WATCH_READ,
&watcher, &handler));
@@ -485,11 +485,12 @@ void StopWatchingAndWatchAgain(MessagePumpForIO::FdWatchController* controller,
// Verify that a watcher can be stopped and reused from an event handler.
TEST_F(FdWatchControllerPosixTest, StopAndRestartFromHandler) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
RunLoop run_loop1;
RunLoop run_loop2;
CallClosureHandler handler2(run_loop2.QuitClosure(), OnceClosure());
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+
CallClosureHandler handler1(BindOnce(&StopWatchingAndWatchAgain, &watcher,
read_fd_.get(), &handler2, &run_loop1),
OnceClosure());
@@ -511,7 +512,6 @@ TEST_F(FdWatchControllerPosixTest, StopAndRestartFromHandler) {
// Verify that the pump properly handles a delayed task after an IO event.
TEST_F(FdWatchControllerPosixTest, IoEventThenTimer) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
RunLoop timer_run_loop;
env.GetMainThreadTaskRunner()->PostDelayedTask(
@@ -521,6 +521,7 @@ TEST_F(FdWatchControllerPosixTest, IoEventThenTimer) {
CallClosureHandler handler(watcher_run_loop.QuitClosure(), OnceClosure());
// Create a non-persistent watcher.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor(
read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
&watcher, &handler));
@@ -540,7 +541,6 @@ TEST_F(FdWatchControllerPosixTest, IoEventThenTimer) {
// Verify that the pipe can handle an IO event after a delayed task.
TEST_F(FdWatchControllerPosixTest, TimerThenIoEvent) {
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
- MessagePumpForIO::FdWatchController watcher(FROM_HERE);
// Trigger read event from a delayed task.
env.GetMainThreadTaskRunner()->PostDelayedTask(
@@ -552,6 +552,7 @@ TEST_F(FdWatchControllerPosixTest, TimerThenIoEvent) {
CallClosureHandler handler(run_loop.QuitClosure(), OnceClosure());
// Create a non-persistent watcher.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor(
read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
&watcher, &handler));
diff --git a/base/message_loop/message_pump_glib.cc b/base/message_loop/message_pump_glib.cc
index ca2dd6b9b..dfc8a9555 100644
--- a/base/message_loop/message_pump_glib.cc
+++ b/base/message_loop/message_pump_glib.cc
@@ -103,6 +103,153 @@ bool RunningOnMainThread() {
// we run DoWork. That part will also run in the other event pumps.
// - We also run DoWork, and possibly DoIdleWork, in the main loop,
// around event handling.
+//
+// ---------------------------------------------------------------------------
+//
+// An overview on the way that we track work items:
+//
+// ScopedDoWorkItems are used by this pump to track native work. They are
+// stored by value in |state_| and are set/cleared as the pump runs. Their
+// setting and clearing is done in the functions
+// {Set,Clear,EnsureSet,EnsureCleared}ScopedWorkItem. Control flow in GLib is
+// quite non-obvious because chrome is not notified when a nested loop is
+// entered/exited. To detect nested loops, MessagePumpGlib uses
+// |state_->do_work_depth| which is incremented when DoWork is entered, and a
+// GLib library function, g_main_depth(), which indicates the current number of
+// Dispatch() calls on the stack. To react to them, two separate
+// ScopedDoWorkItems are used (a standard one used for all native work, and a
+// second one used exclusively for forcing nesting when there is a native loop
+// spinning). Note that `ThreadController` flags all nesting as
+// `Phase::kNested` so separating native and application work while nested isn't
+// supported nor a goal.
+//
+// It should also be noted that a second GSource has been added to GLib,
+// referred to as the "observer" source. It is used because in the case where
+// native work occurs on wakeup that is higher priority than Chrome (all of
+// GTK), chrome won't even get notified that the pump is awake.
+//
+// There are several cases to consider wrt. nesting level and order. In
+// order, we have:
+// A. [root] -> MessagePump::Run() -> native event -> g_main_context_iteration
+// B. [root] -> MessagePump::Run() -> DoWork -> g_main_context_iteration
+// C. [root] -> native -> DoWork -> MessagePump -> [...]
+// The second two cases are identical for our purposes, and the last one turns
+// out to be handled without any extra headache.
+//
+// Consider nesting case A, where native work is called from
+// |g_main_context_iteration()| from the pump, and that native work spins up a
+// loop. For our purposes, this is a nested loop, because control is not
+// returned to the pump once one iteration of the pump is complete. In this
+// case, the pump needs to enter nesting without DoWork being involved at
+// all. This is accomplished using |MessagePumpGlib::NestIfRequired()|, which is
+// called during the Prepare() phase of GLib. As the pump records state on entry
+// and exit from GLib using |OnEntryToGlib| and |OnExitFromGlib|, we can compare
+// |g_main_depth| at |HandlePrepare| with the one before we entered
+// |g_main_context_iteration|. If it is higher, there is a native loop being
+// spun, and |RegisterNesting| is called, forcing nesting by initializing two
+// work items at once. These are destroyed after the exit from
+// |g_main_context_iteration| using |OnExitFromGlib|.
+//
+// Then, considering nesting case B, |state_->do_work_depth| is incremented
+// during any Chrome work, to allow the pump to detect re-entrancy during a
+// chrome work item. This is required because `g_main_depth` is not incremented
+// in any `DoWork` call not occuring during `Dispatch()` (i.e. during
+// `MessagePumpGlib::Run()`). In this case, a nested loop is recorded, and the
+// pump sets-and-clears scoped work items during Prepare, Check, and Dispatch. A
+// work item can never be active when control flow returns to GLib (i.e. on
+// return) during a nested loop, because the nested loop could exit at any
+// point. This is fine because TimeKeeper is only concerned with the fact that a
+// nested loop is in progress, as opposed to the various phases of the nested
+// loop.
+//
+// Finally, consider nesting case C, where a native loop is spinning
+// entirely outside of Chrome, such as inside a signal handler, the pump might
+// create and destroy DoWorkItems during Prepare() and Check(), but these work
+// items will always get cleared during Dispatch(), before the pump enters a
+// DoWork(), leading to the pump showing non-nested native work without the
+// thread controller being active, the correct situation (which won't occur
+// outside of startup or shutdown). Once Dispatch() is called, the pump's
+// nesting tracking works correctly, as state_->do_work_depth is increased, and
+// upon re-entrancy we detect the nested loop, which is correct, as this is the
+// only point at which the loop actually becomes "nested".
+//
+// -----------------------------------------------------------------------------
+//
+// As an overview of the steps taken by MessagePumpGLib to ensure that nested
+// loops are detected adequately during each phase of the GLib loop:
+//
+// 0: Before entering GLib:
+// 0.1: Record state about current state of GLib (g_main_depth()) for
+// case 1.1.2.
+//
+// 1: Prepare.
+// 1.1: Detection of nested loops
+
+// 1.1.1: If |state_->do_work_depth| > 0, we are in nesting case B detailed
+// above. A work item must be newly created during this function to
+// trigger nesting, and is destroyed to ensure proper destruction order
+// in the case where GLib quits after Prepare().
+//
+// 1.1.2: Otherwise, check if we are in nesting case A above. If yes, trigger
+// nesting using ScopedDoWorkItems. The nesting will be cleared at exit
+// from GLib.
+//
+// This check occurs only in |HandleObserverPrepare|, not in
+// |HandlePrepare|.
+//
+// A third party is running a glib message loop. Since Chrome work is
+// registered with GLib at |G_PRIORITY_DEFAULT_IDLE|, a relatively low
+// priority, sources of default-or-higher priority will be Dispatch()ed
+// first. Since only one source is Dispatched per loop iteration,
+// |HandlePrepare| can get called several times in a row in the case that
+// there are any other events in the queue. A ScopedDoWorkItem is created
+// and destroyed to record this. That work item triggers nesting.
+//
+// 1.2: Other considerations
+// 1.2.1: Sleep occurs between Prepare() and Check(). If Chrome will pass a
+// nonzero poll time to GLib, the inner ScopedDoWorkItem is cleared and
+// BeforeWait() is called. In nesting case A, the nesting work item will
+// not be cleared. A nested loop will typically not block.
+//
+// Since Prepare() is called before Check() in all cases, the bulk of
+// nesting detection is done in Prepare().
+//
+// 2: Check.
+// 2.1: Detection of nested loops:
+// 2.1.1: In nesting case B, |ClearScopedWorkItem()| on exit. A third party is
+// running a glib message loop. It is possible that at any point the
+// nested message loop will quit. In this case, we don't want to leave a
+// nested DoWorkItem on the stack.
+//
+// 2.2: Other considerations
+// 2.2.1: A ScopedDoWorkItem may be created (if it was not already present) at
+// the entry to Check() to record a wakeup in the case that the pump
+// slept. It is important to note that this occurs both in
+// |HandleObserverCheck| and |HandleCheck| to ensure that at every point
+// as the pump enters the Dispatch phase it is awake. In the case it is
+// already awake, this is a very cheap operation.
+//
+// 3: Dispatch
+// 3.1 Detection of nested loops
+// 3.1.1: |state_->do_work_depth| is incremented on entry and decremented on
+// exit. This is used to detect nesting case B.
+//
+// 3.1.2: Nested loops can be quit at any point, and so ScopedDoWorkItems can't
+// be left on the stack for the same reasons as in 1.1.1/2.1.1.
+//
+// 3.2 Other considerations
+// 3.2.1: Since DoWork creates its own work items, ScopedDoWorkItems are not
+// used as this would trigger nesting in all cases.
+//
+// 4: Post GLib
+// 4.1: Detection of nested loops
+// 4.1.1: |state_->do_work_depth| is also increased during the DoWork in Run()
+// as nesting in that case [calling glib from third party code] needs to
+// clear all work items after return to avoid improper destruction order.
+//
+// 4.2: Other considerations:
+// 4.2.1: DoWork uses its own work item, so no ScopedDoWorkItems are active in
+// this case.
struct WorkSource : public GSource {
raw_ptr<MessagePumpGlib> pump;
@@ -130,8 +277,28 @@ gboolean WorkSourceDispatch(GSource* source,
}
// I wish these could be const, but g_source_new wants non-const.
-GSourceFuncs WorkSourceFuncs = {WorkSourcePrepare, WorkSourceCheck,
- WorkSourceDispatch, nullptr};
+GSourceFuncs g_work_source_funcs = {WorkSourcePrepare, WorkSourceCheck,
+ WorkSourceDispatch, nullptr};
+
+struct ObserverSource : public GSource {
+ raw_ptr<MessagePumpGlib> pump;
+};
+
+gboolean ObserverPrepare(GSource* gsource, gint* timeout_ms) {
+ auto* source = static_cast<ObserverSource*>(gsource);
+ source->pump->HandleObserverPrepare();
+ *timeout_ms = -1;
+ // We always want to poll.
+ return FALSE;
+}
+
+gboolean ObserverCheck(GSource* gsource) {
+ auto* source = static_cast<ObserverSource*>(gsource);
+ return source->pump->HandleObserverCheck();
+}
+
+GSourceFuncs g_observer_funcs = {ObserverPrepare, ObserverCheck, nullptr,
+ nullptr};
struct FdWatchSource : public GSource {
raw_ptr<MessagePumpGlib> pump;
@@ -162,7 +329,7 @@ GSourceFuncs g_fd_watch_source_funcs = {
} // namespace
struct MessagePumpGlib::RunState {
- explicit RunState(raw_ptr<Delegate> delegate) : delegate(delegate) {
+ explicit RunState(Delegate* delegate) : delegate(delegate) {
CHECK(delegate);
}
@@ -171,6 +338,25 @@ struct MessagePumpGlib::RunState {
// Used to flag that the current Run() invocation should return ASAP.
bool should_quit = false;
+ // Keeps track of the number of calls to DoWork() on the stack for the current
+ // Run() invocation. Used to detect reentrancy from DoWork in order to make
+ // decisions about tracking nested work.
+ int do_work_depth = 0;
+
+ // Value of g_main_depth() captured before the call to
+ // g_main_context_iteration() in Run(). nullopt if Run() is not calling
+ // g_main_context_iteration(). Used to track whether the pump has forced a
+ // nested state due to a native pump.
+ absl::optional<int> g_depth_on_iteration;
+
+ // Used to keep track of the native event work items processed by the message
+ // pump.
+ Delegate::ScopedDoWorkItem scoped_do_work_item;
+
+ // Used to force the pump into a nested state when a native runloop was
+ // dispatched from main.
+ Delegate::ScopedDoWorkItem native_loop_do_work_item;
+
// The information of the next task available at this run-level. Stored in
// RunState because different set of tasks can be accessible at various
// run-levels (e.g. non-nestable tasks).
@@ -199,8 +385,13 @@ MessagePumpGlib::MessagePumpGlib()
wakeup_gpollfd_->fd = wakeup_pipe_read_;
wakeup_gpollfd_->events = G_IO_IN;
+ observer_source_ = std::unique_ptr<GSource, GSourceDeleter>(
+ g_source_new(&g_observer_funcs, sizeof(ObserverSource)));
+ static_cast<ObserverSource*>(observer_source_.get())->pump = this;
+ g_source_attach(observer_source_.get(), context_);
+
work_source_ = std::unique_ptr<GSource, GSourceDeleter>(
- g_source_new(&WorkSourceFuncs, sizeof(WorkSource)));
+ g_source_new(&g_work_source_funcs, sizeof(WorkSource)));
static_cast<WorkSource*>(work_source_.get())->pump = this;
g_source_add_poll(work_source_.get(), wakeup_gpollfd_.get());
g_source_set_priority(work_source_.get(), kPriorityWork);
@@ -327,19 +518,74 @@ bool MessagePumpGlib::WatchFileDescriptor(int fd,
return controller->Attach(this);
}
+void MessagePumpGlib::HandleObserverPrepare() {
+ // |state_| may be null during tests.
+ if (!state_) {
+ return;
+ }
+
+ if (state_->do_work_depth > 0) {
+ // Contingency 1.1.1 detailed above
+ SetScopedWorkItem();
+ ClearScopedWorkItem();
+ } else {
+ // Contingency 1.1.2 detailed above
+ NestIfRequired();
+ }
+
+ return;
+}
+
+bool MessagePumpGlib::HandleObserverCheck() {
+ // |state_| may be null in tests.
+ if (!state_) {
+ return FALSE;
+ }
+
+ // Make sure we record the fact that we're awake. Chrome won't get Check()ed
+ // if a higher priority work item returns TRUE from Check().
+ EnsureSetScopedWorkItem();
+ if (state_->do_work_depth > 0) {
+ // Contingency 2.1.1
+ ClearScopedWorkItem();
+ }
+
+ // The observer never needs to run anything.
+ return FALSE;
+}
+
// Return the timeout we want passed to poll.
int MessagePumpGlib::HandlePrepare() {
// |state_| may be null during tests.
if (!state_)
return 0;
- return GetTimeIntervalMilliseconds(state_->next_work_info.delayed_run_time);
+ const int next_wakeup_millis =
+ GetTimeIntervalMilliseconds(state_->next_work_info.delayed_run_time);
+ if (next_wakeup_millis != 0) {
+ // When this is called, it is not possible to know for sure if a
+ // ScopedWorkItem is on the stack, because HandleObserverCheck may have set
+ // it during an iteration of the pump where a high priority native work item
+ // executed.
+ EnsureClearedScopedWorkItem();
+ state_->delegate->BeforeWait();
+ }
+
+ return next_wakeup_millis;
}
bool MessagePumpGlib::HandleCheck() {
if (!state_) // state_ may be null during tests.
return false;
+ // Ensure pump is awake.
+ EnsureSetScopedWorkItem();
+
+ if (state_->do_work_depth > 0) {
+ // Contingency 2.1.1
+ ClearScopedWorkItem();
+ }
+
// We usually have a single message on the wakeup pipe, since we are only
// signaled when the queue went from empty to non-empty, but there can be
// two messages if a task posted a task, hence we read at most two bytes.
@@ -371,7 +617,18 @@ bool MessagePumpGlib::HandleCheck() {
}
void MessagePumpGlib::HandleDispatch() {
+ // Contingency 3.2.1
+ EnsureClearedScopedWorkItem();
+
+ // Contingency 3.1.1
+ ++state_->do_work_depth;
state_->next_work_info = state_->delegate->DoWork();
+ --state_->do_work_depth;
+
+ if (state_ && state_->do_work_depth > 0) {
+ // Contingency 3.1.2
+ EnsureClearedScopedWorkItem();
+ }
}
void MessagePumpGlib::Run(Delegate* delegate) {
@@ -391,14 +648,28 @@ void MessagePumpGlib::Run(Delegate* delegate) {
// callbacks. This is so we only quit our own loops, and we don't quit
// nested loops run by others. TODO(deanm): Is this what we want?
for (;;) {
+ // ScopedWorkItem to account for any native work until the runloop starts
+ // running chrome work.
+ SetScopedWorkItem();
+
// Don't block if we think we have more work to do.
bool block = !more_work_is_plausible;
+ OnEntryToGlib();
more_work_is_plausible = g_main_context_iteration(context_, block);
+ OnExitFromGlib();
+
if (state_->should_quit)
break;
+ // Contingency 4.2.1
+ EnsureClearedScopedWorkItem();
+
+ // Contingency 4.1.1
+ ++state_->do_work_depth;
state_->next_work_info = state_->delegate->DoWork();
+ --state_->do_work_depth;
+
more_work_is_plausible |= state_->next_work_info.is_immediate();
if (state_->should_quit)
break;
@@ -471,4 +742,128 @@ bool MessagePumpGlib::ShouldQuit() const {
return state_->should_quit;
}
+void MessagePumpGlib::SetScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ // If there exists a ScopedDoWorkItem in the current RunState, it cannot be
+ // overwritten.
+ CHECK(state_->scoped_do_work_item.IsNull());
+
+ // In the case that we're more than two work items deep, don't bother tracking
+ // individual native events anymore. Note that this won't cause out-of-order
+ // end work items, because the work item is cleared before entering the second
+ // DoWork().
+ if (state_->do_work_depth < 2) {
+ state_->scoped_do_work_item = state_->delegate->BeginWorkItem();
+ }
+}
+
+void MessagePumpGlib::ClearScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+
+ CHECK(!state_->scoped_do_work_item.IsNull());
+ // See identical check in SetScopedWorkItem
+ if (state_->do_work_depth < 2) {
+ state_->scoped_do_work_item = Delegate::ScopedDoWorkItem();
+ }
+}
+
+void MessagePumpGlib::EnsureSetScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (state_->scoped_do_work_item.IsNull()) {
+ SetScopedWorkItem();
+ }
+}
+
+void MessagePumpGlib::EnsureClearedScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (!state_->scoped_do_work_item.IsNull()) {
+ ClearScopedWorkItem();
+ }
+}
+
+void MessagePumpGlib::RegisterNested() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ CHECK(state_->native_loop_do_work_item.IsNull());
+
+ // Transfer `scoped_do_work_item` to `native_do_work_item`, and so the
+ // ephemeral `scoped_do_work_item` will be coming in and out of existence on
+ // top of `native_do_work_item`, whose state hasn't been deleted.
+
+ if (state_->scoped_do_work_item.IsNull()) {
+ state_->native_loop_do_work_item = state_->delegate->BeginWorkItem();
+ } else {
+ // This clears state_->scoped_do_work_item.
+ state_->native_loop_do_work_item = std::move(state_->scoped_do_work_item);
+ }
+ SetScopedWorkItem();
+ ClearScopedWorkItem();
+}
+
+void MessagePumpGlib::UnregisterNested() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ CHECK(!state_->native_loop_do_work_item.IsNull());
+
+ EnsureClearedScopedWorkItem();
+ // Nesting exits here.
+ state_->native_loop_do_work_item = Delegate::ScopedDoWorkItem();
+}
+
+void MessagePumpGlib::NestIfRequired() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (state_->native_loop_do_work_item.IsNull() &&
+ state_->g_depth_on_iteration.has_value() &&
+ g_main_depth() != state_->g_depth_on_iteration.value()) {
+ RegisterNested();
+ }
+}
+
+void MessagePumpGlib::UnnestIfRequired() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (!state_->native_loop_do_work_item.IsNull()) {
+ UnregisterNested();
+ }
+}
+
+void MessagePumpGlib::OnEntryToGlib() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ CHECK(!state_->g_depth_on_iteration.has_value());
+ state_->g_depth_on_iteration.emplace(g_main_depth());
+}
+
+void MessagePumpGlib::OnExitFromGlib() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ state_->g_depth_on_iteration.reset();
+ UnnestIfRequired();
+}
+
} // namespace base
diff --git a/base/message_loop/message_pump_glib.h b/base/message_loop/message_pump_glib.h
index 1b0fcced8..647f3cc45 100644
--- a/base/message_loop/message_pump_glib.h
+++ b/base/message_loop/message_pump_glib.h
@@ -93,6 +93,12 @@ class BASE_EXPORT MessagePumpGlib : public MessagePump,
bool HandleCheck();
void HandleDispatch();
+ // Very similar to the above, with the key difference that these functions are
+ // only used to track work items and never indicate work is available, and
+ // poll indefinitely.
+ void HandleObserverPrepare();
+ bool HandleObserverCheck();
+
// Overridden from MessagePump:
void Run(Delegate* delegate) override;
void Quit() override;
@@ -131,6 +137,38 @@ class BASE_EXPORT MessagePumpGlib : public MessagePump,
raw_ptr<RunState> state_;
+ // Starts tracking a new work item and stores a `ScopedDoWorkItem` in
+ // `state_`.
+ void SetScopedWorkItem();
+ // Gets rid of the current scoped work item.
+ void ClearScopedWorkItem();
+ // Ensures there's a ScopedDoWorkItem at the current run-level. This can be
+ // useful for contexts where the caller can't tell whether they just woke up
+ // or are continuing from native work.
+ void EnsureSetScopedWorkItem();
+ // Ensures there's no ScopedDoWorkItem at the current run-level. This can be
+ // useful in contexts where the caller knows that a sleep is imminent but
+ // doesn't know if the current context captures ongoing work (back from
+ // native).
+ void EnsureClearedScopedWorkItem();
+
+ // Called before entrance to g_main_context_iteration to record context
+ // related to nesting depth to track native nested loops which would otherwise
+ // be invisible.
+ void OnEntryToGlib();
+ // Cleans up state set in OnEntryToGlib.
+ void OnExitFromGlib();
+ // Forces the pump into a nested state by creating two work items back to
+ // back.
+ void RegisterNested();
+ // Removes all of the pump's ScopedDoWorkItems to remove the state of nesting
+ // which was forced onto the pump.
+ void UnregisterNested();
+ // Nest if pump is not already marked as nested.
+ void NestIfRequired();
+ // Remove the nesting if the pump is nested.
+ void UnnestIfRequired();
+
std::unique_ptr<GMainContext, GMainContextDeleter> owned_context_;
// This is a GLib structure that we can add event sources to. On the main
// thread, we use the default GLib context, which is the one to which all GTK
@@ -141,6 +179,10 @@ class BASE_EXPORT MessagePumpGlib : public MessagePump,
// the message pump is destroyed.
std::unique_ptr<GSource, GSourceDeleter> work_source_;
+ // The observer source. It is shared by all calls to Run and destroyed when
+ // the message pump is destroyed.
+ std::unique_ptr<GSource, GSourceDeleter> observer_source_;
+
// We use a wakeup pipe to make sure we'll get out of the glib polling phase
// when another thread has scheduled us to do some work. There is a glib
// mechanism g_main_context_wakeup, but this won't guarantee that our event's
diff --git a/base/message_loop/message_pump_glib_unittest.cc b/base/message_loop/message_pump_glib_unittest.cc
index b202b4092..77b5ca425 100644
--- a/base/message_loop/message_pump_glib_unittest.cc
+++ b/base/message_loop/message_pump_glib_unittest.cc
@@ -6,6 +6,7 @@
#include <glib.h>
#include <math.h>
+#include "build/build_config.h"
#include <algorithm>
#include <vector>
@@ -27,6 +28,7 @@
#include "base/task/single_thread_task_executor.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
+#include "base/test/trace_event_analyzer.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -540,6 +542,67 @@ TEST_F(MessagePumpGLibTest, TestGtkLoop) {
run_loop.Run();
}
+namespace {
+
+class NestedEventAnalyzer {
+ public:
+ NestedEventAnalyzer() {
+ trace_analyzer::Start(TRACE_DISABLED_BY_DEFAULT("base"));
+ }
+
+ size_t CountEvents() {
+ std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer =
+ trace_analyzer::Stop();
+ trace_analyzer::TraceEventVector events;
+ return analyzer->FindEvents(trace_analyzer::Query::EventName() ==
+ trace_analyzer::Query::String("Nested"),
+ &events);
+ }
+};
+
+} // namespace
+
+TEST_F(MessagePumpGLibTest, TestNativeNestedLoopWithoutDoWork) {
+ // Tests that nesting is triggered correctly if a message loop is run
+ // from a native event (gtk event) outside of a work item (not in a posted
+ // task).
+
+ RunLoop run_loop;
+ NestedEventAnalyzer analyzer;
+
+ base::CurrentThread::Get()->EnableMessagePumpTimeKeeperMetrics(
+ "GlibMainLoopTest");
+
+ scoped_refptr<GLibLoopRunner> runner = base::MakeRefCounted<GLibLoopRunner>();
+ injector()->AddEvent(
+ 0,
+ BindOnce(
+ [](EventInjector* injector, scoped_refptr<GLibLoopRunner> runner,
+ OnceClosure done) {
+ CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
+ runner->RunLoop();
+ },
+ Unretained(injector()), runner, run_loop.QuitClosure()));
+
+ injector()->AddDummyEvent(0);
+ injector()->AddDummyEvent(0);
+ injector()->AddDummyEvent(0);
+
+ SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE, BindOnce(&GLibLoopRunner::Quit, runner), Milliseconds(40));
+
+ SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), Milliseconds(40));
+
+ run_loop.Run();
+
+ // It would be expected that there be one single event, but it seems like this
+ // is counting the Begin/End of the Nested trace event. Each of the two events
+ // found are of duration 0 with distinct timestamps. It has also been
+ // confirmed that nesting occurs only once.
+ CHECK_EQ(analyzer.CountEvents(), 2ul);
+}
+
// Tests for WatchFileDescriptor API
class MessagePumpGLibFdWatchTest : public testing::Test {
protected:
diff --git a/base/message_loop/message_pump_libevent.h b/base/message_loop/message_pump_libevent.h
index 4017d93a1..0389de964 100644
--- a/base/message_loop/message_pump_libevent.h
+++ b/base/message_loop/message_pump_libevent.h
@@ -162,7 +162,10 @@ class BASE_EXPORT MessagePumpLibevent : public MessagePump,
// State used only with libevent
std::unique_ptr<event> event_;
- raw_ptr<MessagePumpLibevent> libevent_pump_ = nullptr;
+
+ // Tests (e.g. FdWatchControllerPosixTest) deliberately make this dangle.
+ raw_ptr<MessagePumpLibevent, DisableDanglingPtrDetection> libevent_pump_ =
+ nullptr;
// State used only with epoll
WeakPtr<MessagePumpEpoll> epoll_pump_;
diff --git a/base/message_loop/message_pump_unittest.cc b/base/message_loop/message_pump_unittest.cc
index 9e1970387..b11167b42 100644
--- a/base/message_loop/message_pump_unittest.cc
+++ b/base/message_loop/message_pump_unittest.cc
@@ -139,6 +139,13 @@ class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> {
MessagePumpTest() : message_pump_(MessagePump::Create(GetParam())) {}
protected:
+#if defined(USE_GLIB)
+ // Because of a GLIB implementation quirk, the pump doesn't do the same things
+ // between each DoWork. In this case, it won't set/clear a ScopedDoWorkItem
+ // because we run a chrome work item in the runloop outside of GLIB's control,
+ // so we oscillate between setting and not setting PreDoWorkExpectations.
+ std::map<MessagePump::Delegate*, int> do_work_counts;
+#endif
void AddPreDoWorkExpectations(
testing::StrictMock<MockMessagePumpDelegate>& delegate) {
#if BUILDFLAG(IS_WIN)
@@ -155,14 +162,35 @@ class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> {
EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
}
#endif // BUILDFLAG(IS_WIN)
+#if defined(USE_GLIB)
+ do_work_counts.try_emplace(&delegate, 0);
+ if (GetParam() == MessagePumpType::UI) {
+ if (++do_work_counts[&delegate] % 2) {
+ // The GLib MessagePump will do native work before chrome work on
+ // startup.
+ EXPECT_CALL(delegate, MockOnBeginWorkItem);
+ EXPECT_CALL(delegate, MockOnEndWorkItem);
+ }
+ }
+#endif // defined(USE_GLIB)
}
void AddPostDoWorkExpectations(
testing::StrictMock<MockMessagePumpDelegate>& delegate) {
+#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
// MessagePumpLibEvent checks for native notifications once after processing
// a DoWork() but only instantiates a ScopedDoWorkItem that triggers
// MessagePumpLibevent::OnLibeventNotification() which this test does not
// so there are no post-work expectations at the moment.
+#endif
+#if defined(USE_GLIB)
+ if (GetParam() == MessagePumpType::UI) {
+ // The GLib MessagePump can create and destroy work items between DoWorks
+ // depending on internal state.
+ EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
+ EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
+ }
+#endif // defined(USE_GLIB)
}
std::unique_ptr<MessagePump> message_pump_;
@@ -182,6 +210,16 @@ TEST_P(MessagePumpTest, QuitStopsWork) {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
+
+ // MessagePumpGlib uses a work item between a HandleDispatch() call and
+ // passing control back to the chrome loop, which handles the Quit() despite
+ // us not necessarily doing any native work during that time.
+#if defined(USE_GLIB)
+ if (GetParam() == MessagePumpType::UI) {
+ AddPostDoWorkExpectations(delegate);
+ }
+#endif
+
EXPECT_CALL(delegate, DoIdleWork()).Times(0);
message_pump_->ScheduleWork();
@@ -215,10 +253,14 @@ TEST_P(MessagePumpTest, QuitStopsWorkWithNestedRunLoop) {
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
- // PostDoWorkExpectations for the first DoWork.
+ // The `nested_delegate` will quit first.
+ AddPostDoWorkExpectations(nested_delegate);
+
+ // Return a delayed task with |yield_to_native| set, and exit.
AddPostDoWorkExpectations(delegate);
AddPreDoWorkExpectations(delegate);
+
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
@@ -245,8 +287,8 @@ TEST_P(MessagePumpTest, YieldToNativeRequestedSmokeTest) {
}));
AddPostDoWorkExpectations(delegate);
- // Return a delayed task with |yield_to_native| set, and exit.
AddPreDoWorkExpectations(delegate);
+ // Return a delayed task with |yield_to_native| set, and exit.
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
auto now = TimeTicks::Now();
@@ -357,9 +399,13 @@ TEST_P(MessagePumpTest, RunWithoutScheduleWorkInvokesDoWork) {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
+
+ AddPostDoWorkExpectations(delegate);
+
#if BUILDFLAG(IS_IOS)
EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
#endif
+
message_pump_->Run(&delegate);
}
@@ -383,6 +429,11 @@ TEST_P(MessagePumpTest, NestedRunWithoutScheduleWorkInvokesDoWork) {
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
+ // We quit `nested_delegate` before `delegate`
+ AddPostDoWorkExpectations(nested_delegate);
+
+ AddPostDoWorkExpectations(delegate);
+
#if BUILDFLAG(IS_IOS)
EXPECT_CALL(nested_delegate, DoIdleWork).Times(AnyNumber());
EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
diff --git a/base/message_loop/message_pump_win.h b/base/message_loop/message_pump_win.h
index 1ea4f1d9f..68afdb236 100644
--- a/base/message_loop/message_pump_win.h
+++ b/base/message_loop/message_pump_win.h
@@ -14,6 +14,7 @@
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ptr_exclusion.h"
#include "base/message_loop/message_pump.h"
#include "base/observer_list.h"
#include "base/threading/thread_checker.h"
@@ -76,7 +77,9 @@ class BASE_EXPORT MessagePumpWin : public MessagePump {
std::atomic_bool work_scheduled_{false};
// State for the current invocation of Run(). null if not running.
- RunState* run_state_ = nullptr;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #addr-of
+ RAW_PTR_EXCLUSION RunState* run_state_ = nullptr;
THREAD_CHECKER(bound_thread_);
};
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index 06cb7bf93..fa6a8cc6c 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -372,13 +372,14 @@ FieldTrial* FieldTrial::CreateSimulatedFieldTrial(
StringPiece default_group_name,
double entropy_value) {
return new FieldTrial(trial_name, total_probability, default_group_name,
- entropy_value);
+ entropy_value, /*is_low_anonymity=*/false);
}
FieldTrial::FieldTrial(StringPiece trial_name,
const Probability total_probability,
StringPiece default_group_name,
- double entropy_value)
+ double entropy_value,
+ bool is_low_anonymity)
: trial_name_(trial_name),
divisor_(total_probability),
default_group_name_(default_group_name),
@@ -389,7 +390,8 @@ FieldTrial::FieldTrial(StringPiece trial_name,
forced_(false),
group_reported_(false),
trial_registered_(false),
- ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull) {
+ ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull),
+ is_low_anonymity_(is_low_anonymity) {
DCHECK_GT(total_probability, 0);
DCHECK(!trial_name_.empty());
DCHECK(!default_group_name_.empty())
@@ -472,7 +474,8 @@ FieldTrial* FieldTrialList::FactoryGetFieldTrial(
FieldTrial::Probability total_probability,
StringPiece default_group_name,
const FieldTrial::EntropyProvider& entropy_provider,
- uint32_t randomization_seed) {
+ uint32_t randomization_seed,
+ bool is_low_anonymity) {
// Check if the field trial has already been created in some other way.
FieldTrial* existing_trial = Find(trial_name);
if (existing_trial) {
@@ -483,8 +486,9 @@ FieldTrial* FieldTrialList::FactoryGetFieldTrial(
double entropy_value =
entropy_provider.GetEntropyForTrial(trial_name, randomization_seed);
- FieldTrial* field_trial = new FieldTrial(trial_name, total_probability,
- default_group_name, entropy_value);
+ FieldTrial* field_trial =
+ new FieldTrial(trial_name, total_probability, default_group_name,
+ entropy_value, is_low_anonymity);
FieldTrialList::Register(field_trial, /*is_randomized_trial=*/true);
return field_trial;
}
@@ -609,16 +613,8 @@ std::string FieldTrialList::AllParamsToString(EscapeDataFunc encode_data_func) {
// static
void FieldTrialList::GetActiveFieldTrialGroups(
FieldTrial::ActiveGroups* active_groups) {
- DCHECK(active_groups->empty());
- if (!global_)
- return;
- AutoLock auto_lock(global_->lock_);
-
- for (const auto& registered : global_->registered_) {
- FieldTrial::ActiveGroup active_group;
- if (registered.second->GetActiveGroup(&active_group))
- active_groups->push_back(active_group);
- }
+ GetActiveFieldTrialGroupsInternal(active_groups,
+ /*include_low_anonymity=*/false);
}
// static
@@ -805,7 +801,8 @@ FieldTrialList::DuplicateFieldTrialSharedMemoryForTesting() {
// static
FieldTrial* FieldTrialList::CreateFieldTrial(StringPiece name,
- StringPiece group_name) {
+ StringPiece group_name,
+ bool is_low_anonymity) {
DCHECK(global_);
DCHECK_GE(name.size(), 0u);
DCHECK_GE(group_name.size(), 0u);
@@ -821,7 +818,8 @@ FieldTrial* FieldTrialList::CreateFieldTrial(StringPiece name,
return field_trial;
}
const int kTotalProbability = 100;
- field_trial = new FieldTrial(name, kTotalProbability, group_name, 0);
+ field_trial =
+ new FieldTrial(name, kTotalProbability, group_name, 0, is_low_anonymity);
// The group choice will be finalized in this method. So
// |is_randomized_trial| should be false.
FieldTrialList::Register(field_trial, /*is_randomized_trial=*/false);
@@ -832,21 +830,14 @@ FieldTrial* FieldTrialList::CreateFieldTrial(StringPiece name,
// static
bool FieldTrialList::AddObserver(Observer* observer) {
- if (!global_)
- return false;
- AutoLock auto_lock(global_->lock_);
- global_->observers_.push_back(observer);
- return true;
+ return FieldTrialList::AddObserverInternal(observer,
+ /*include_low_anonymity=*/false);
}
// static
void FieldTrialList::RemoveObserver(Observer* observer) {
- if (!global_)
- return;
- AutoLock auto_lock(global_->lock_);
- Erase(global_->observers_, observer);
- DCHECK_EQ(global_->num_ongoing_notify_field_trial_group_selection_calls_, 0)
- << "Cannot call RemoveObserver while accessing FieldTrial::group_name().";
+ FieldTrialList::RemoveObserverInternal(observer,
+ /*include_low_anonymity=*/false);
}
// static
@@ -855,6 +846,7 @@ void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) {
return;
std::vector<Observer*> local_observers;
+ std::vector<Observer*> local_observers_including_low_anonymity;
{
AutoLock auto_lock(global_->lock_);
@@ -870,9 +862,18 @@ void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) {
// lock. Since removing observers concurrently with this method is
// disallowed, pointers should remain valid while observers are notified.
local_observers = global_->observers_;
+ local_observers_including_low_anonymity =
+ global_->observers_including_low_anonymity_;
+ }
+
+ if (!field_trial->is_low_anonymity_) {
+ for (Observer* observer : local_observers) {
+ observer->OnFieldTrialGroupFinalized(field_trial->trial_name(),
+ field_trial->group_name_internal());
+ }
}
- for (Observer* observer : local_observers) {
+ for (Observer* observer : local_observers_including_low_anonymity) {
observer->OnFieldTrialGroupFinalized(field_trial->trial_name(),
field_trial->group_name_internal());
}
@@ -1399,4 +1400,55 @@ bool FieldTrialList::CreateTrialsFromFieldTrialStatesInternal(
return true;
}
+// static
+void FieldTrialList::GetActiveFieldTrialGroupsInternal(
+ FieldTrial::ActiveGroups* active_groups,
+ bool include_low_anonymity) {
+ DCHECK(active_groups->empty());
+ if (!global_) {
+ return;
+ }
+ AutoLock auto_lock(global_->lock_);
+
+ for (const auto& registered : global_->registered_) {
+ const FieldTrial& trial = *registered.second;
+ FieldTrial::ActiveGroup active_group;
+ if ((include_low_anonymity || !trial.is_low_anonymity_) &&
+ trial.GetActiveGroup(&active_group)) {
+ active_groups->push_back(active_group);
+ }
+ }
+}
+
+// static
+bool FieldTrialList::AddObserverInternal(Observer* observer,
+ bool include_low_anonymity) {
+ if (!global_) {
+ return false;
+ }
+ AutoLock auto_lock(global_->lock_);
+ if (include_low_anonymity) {
+ global_->observers_including_low_anonymity_.push_back(observer);
+ } else {
+ global_->observers_.push_back(observer);
+ }
+ return true;
+}
+
+// static
+void FieldTrialList::RemoveObserverInternal(Observer* observer,
+ bool include_low_anonymity) {
+ if (!global_) {
+ return;
+ }
+ AutoLock auto_lock(global_->lock_);
+ if (include_low_anonymity) {
+ Erase(global_->observers_including_low_anonymity_, observer);
+ } else {
+ Erase(global_->observers_, observer);
+ }
+ DCHECK_EQ(global_->num_ongoing_notify_field_trial_group_selection_calls_, 0)
+ << "Cannot call RemoveObserver while accessing FieldTrial::group_name().";
+}
+
} // namespace base
diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h
index d62b550fc..bc805254b 100644
--- a/base/metrics/field_trial.h
+++ b/base/metrics/field_trial.h
@@ -99,6 +99,7 @@ namespace test {
class ScopedFeatureList;
} // namespace test
+class CompareActiveGroupToFieldTrialMatcher;
class FieldTrialList;
struct LaunchOptions;
@@ -263,6 +264,12 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
StringPiece default_group_name,
double entropy_value);
+ // Whether this field trial is low anonymity or not (see
+ // |FieldTrialListIncludingLowAnonymity|).
+ // TODO(crbug.com/1431156): remove this once all call sites have been properly
+ // migrated to use an appropriate observer.
+ bool is_low_anonymity() { return is_low_anonymity_; }
+
private:
// Allow tests to access our innards for testing purposes.
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Registration);
@@ -290,6 +297,10 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, ClearParamsFromSharedMemory);
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest,
TestGetRandomizedFieldTrialCount);
+ FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetLowAnonymity);
+
+ // MATCHER(CompareActiveGroupToFieldTrialMatcher, "")
+ friend class base::CompareActiveGroupToFieldTrialMatcher;
friend class base::FieldTrialList;
@@ -307,7 +318,8 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
FieldTrial(StringPiece trial_name,
Probability total_probability,
StringPiece default_group_name,
- double entropy_value);
+ double entropy_value,
+ bool is_low_anonymity);
virtual ~FieldTrial();
@@ -384,6 +396,10 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
// Denotes whether benchmarking is enabled. In this case, field trials all
// revert to the default group.
static bool enable_benchmarking_;
+
+ // Whether this field trial is potentially low anonymity (eg. only a small
+ // set of users are included).
+ const bool is_low_anonymity_ = false;
};
//------------------------------------------------------------------------------
@@ -436,6 +452,11 @@ class BASE_EXPORT FieldTrialList {
// * SHA1 and NormalizedMurmurHash providers will use a non-zero value as a
// salt _instead_ of using the trial name.
//
+ // Some field trials may be targeted in such way that a relatively small
+ // number of users are in a particular experiment group. Such trials should
+ // have |is_low_anonymity| set to true, and their visitbility is restricted
+ // to specific callers only, via |FieldTrialListIncludingLowAnonymity|.
+ //
// This static method can be used to get a startup-randomized FieldTrial or a
// previously created forced FieldTrial.
static FieldTrial* FactoryGetFieldTrial(
@@ -443,7 +464,8 @@ class BASE_EXPORT FieldTrialList {
FieldTrial::Probability total_probability,
StringPiece default_group_name,
const FieldTrial::EntropyProvider& entropy_provider,
- uint32_t randomization_seed = 0);
+ uint32_t randomization_seed = 0,
+ bool is_low_anonymity = false);
// The Find() method can be used to test to see if a named trial was already
// registered, or to retrieve a pointer to it from the global map.
@@ -486,6 +508,10 @@ class BASE_EXPORT FieldTrialList {
// called) with a snapshot of all registered FieldTrials for which the group
// has been chosen and externally observed (via |group()|) and which have
// not been disabled.
+ //
+ // This does not return low anonymity field trials. Callers who need access to
+ // low anonymity field trials should use
+ // |FieldTrialListIncludingLowAnonymity.GetActiveFieldTrialGroups()|.
static void GetActiveFieldTrialGroups(
FieldTrial::ActiveGroups* active_groups);
@@ -554,18 +580,30 @@ class BASE_EXPORT FieldTrialList {
// randomly selected state in a browser process into this non-browser process.
// It returns NULL if there is a FieldTrial that is already registered with
// the same |name| but has different finalized group string (|group_name|).
- static FieldTrial* CreateFieldTrial(StringPiece name, StringPiece group_name);
+ //
+ // Visibility of field trials with |is_low_anonymity| set to true is
+ // restricted to specific callers only, see
+ // |FieldTrialListIncludingLowAnonymity|.
+ static FieldTrial* CreateFieldTrial(StringPiece name,
+ StringPiece group_name,
+ bool is_low_anonymity = false);
// Add an observer to be notified when a field trial is irrevocably committed
// to being part of some specific field_group (and hence the group_name is
// also finalized for that field_trial). Returns false and does nothing if
// there is no FieldTrialList singleton. The observer can be notified on any
// sequence; it must be thread-safe.
+ //
+ // Low anonymity field trials are not notified to this observer. Callers
+ // who need to be notified of low anonymity field trials should use
+ // |FieldTrialListIncludingLowAnonymity.AddObserver()|.
static bool AddObserver(Observer* observer);
// Remove an observer. This cannot be invoked concurrently with
// FieldTrial::group() (typically, this means that no other thread should be
// running when this is invoked).
+ //
+ // Removes observers added via the |AddObserver()| method of this class.
static void RemoveObserver(Observer* observer);
// Notify all observers that a group has been finalized for |field_trial|.
@@ -646,6 +684,10 @@ class BASE_EXPORT FieldTrialList {
friend int SerializeSharedMemoryRegionMetadata();
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, CheckReadOnlySharedMemoryRegion);
+ // Required so that |FieldTrialListIncludingLowAnonymity| can expose APIs from
+ // this class to its friends.
+ friend class FieldTrialListIncludingLowAnonymity;
+
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_IOS)
// Serialization is used to pass information about the shared memory handle
// to child processes. This is achieved by passing a stringified reference to
@@ -730,9 +772,32 @@ class BASE_EXPORT FieldTrialList {
static bool CreateTrialsFromFieldTrialStatesInternal(
const std::vector<FieldTrial::State>& entries);
+ // The same as |GetActiveFieldTrialGroups| but also gives access to low
+ // anonymity field trials.
+ // Restricted to specifically allowed friends - access via
+ // |FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups|.
+ static void GetActiveFieldTrialGroupsInternal(
+ FieldTrial::ActiveGroups* active_groups,
+ bool include_low_anonymity);
+
+ // The same as |AddObserver| but is notified for low anonymity field trials
+ // too.
+ // Restricted to specifically allowed friends - access via
+ // |FieldTrialListIncludingLowAnonymity::AddObserver|.
+ static bool AddObserverInternal(Observer* observer,
+ bool include_low_anonymity);
+
+ // The same as |RemoveObserver| but is notified for low anonymity field trials
+ // too.
+ // Restricted to specifically allowed friends - access via
+ // |FieldTrialListIncludingLowAnonymity::RemoveObserver|.
+ static void RemoveObserverInternal(Observer* observer,
+ bool include_low_anonymity);
+
static FieldTrialList* global_; // The singleton of this class.
// Lock for access to |registered_|, |observers_|,
+ // |observers_including_low_anonymity_|,
// |count_of_manually_created_field_trials_|.
Lock lock_;
RegistrationMap registered_ GUARDED_BY(lock_);
@@ -741,8 +806,13 @@ class BASE_EXPORT FieldTrialList {
size_t num_registered_randomized_trials_ GUARDED_BY(lock_) = 0;
// List of observers to be notified when a group is selected for a FieldTrial.
+ // Excludes low anonymity field trials.
std::vector<Observer*> observers_ GUARDED_BY(lock_);
+ // List of observers to be notified when a group is selected for a FieldTrial.
+ // Includes low anonymity field trials.
+ std::vector<Observer*> observers_including_low_anonymity_ GUARDED_BY(lock_);
+
// Counts the ongoing calls to
// FieldTrialList::NotifyFieldTrialGroupSelection(). Used to ensure that
// RemoveObserver() isn't called while notifying observers.
diff --git a/base/metrics/field_trial_list_including_low_anonymity.cc b/base/metrics/field_trial_list_including_low_anonymity.cc
new file mode 100644
index 000000000..d672b865d
--- /dev/null
+++ b/base/metrics/field_trial_list_including_low_anonymity.cc
@@ -0,0 +1,31 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/metrics/field_trial_list_including_low_anonymity.h"
+#include "base/metrics/field_trial.h"
+
+namespace base {
+
+// static
+void FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups(
+ FieldTrial::ActiveGroups* active_groups) {
+ return FieldTrialList::GetActiveFieldTrialGroupsInternal(
+ active_groups, /*include_low_anonymity=*/true);
+}
+
+// static
+bool FieldTrialListIncludingLowAnonymity::AddObserver(
+ FieldTrialList::Observer* observer) {
+ return FieldTrialList::AddObserverInternal(observer,
+ /*include_low_anonymity=*/true);
+}
+
+// static
+void FieldTrialListIncludingLowAnonymity::RemoveObserver(
+ FieldTrialList::Observer* observer) {
+ FieldTrialList::RemoveObserverInternal(observer,
+ /*include_low_anonymity=*/true);
+}
+
+} // namespace base
diff --git a/base/metrics/field_trial_list_including_low_anonymity.h b/base/metrics/field_trial_list_including_low_anonymity.h
new file mode 100644
index 000000000..a70af448d
--- /dev/null
+++ b/base/metrics/field_trial_list_including_low_anonymity.h
@@ -0,0 +1,98 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_METRICS_FIELD_TRIAL_LIST_INCLUDING_LOW_ANONYMITY_H_
+#define BASE_METRICS_FIELD_TRIAL_LIST_INCLUDING_LOW_ANONYMITY_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/metrics/field_trial.h"
+#include "base/values.h"
+
+class AndroidFieldTrialListLogActiveTrialsFriendHelper;
+
+namespace content {
+class FieldTrialSynchronizer;
+}
+
+namespace variations {
+class ChildProcessFieldTrialSyncer;
+class EntropyProviders;
+class ProcessedStudy;
+struct SeedSimulationResult;
+class VariationsCrashKeys;
+class VariationsLayers;
+SeedSimulationResult ComputeDifferences(
+ const std::vector<ProcessedStudy>& processed_studies,
+ const VariationsLayers& layers,
+ const EntropyProviders& entropy_providers);
+} // namespace variations
+
+namespace version_ui {
+base::Value::List GetVariationsList();
+}
+
+namespace base {
+
+// Provides a way to restrict access to the full set of field trials, including
+// trials with low anonymity, to explicitly allowed callers.
+//
+// See |FieldTrialList::FactoryGetFieldTrial()| for background.
+class BASE_EXPORT FieldTrialListIncludingLowAnonymity {
+ public:
+ // Exposed publicly, to avoid test code needing to be explicitly friended.
+ static void GetActiveFieldTrialGroupsForTesting(
+ FieldTrial::ActiveGroups* active_groups) {
+ return GetActiveFieldTrialGroups(active_groups);
+ }
+
+ // Classes / functions which are allowed full access to all field trials
+ // should be listed as friends here, with a comment explaining why this does
+ // not risk revealing identifiable information externally.
+
+ // This is used only for local logging on Android.
+ friend class ::AndroidFieldTrialListLogActiveTrialsFriendHelper;
+
+ // Used to synchronize field trial status between the browser and child
+ // processes.
+ // Access to these trials within each of these is then allowed only to the
+ // other friend classes / methods listed here.
+ friend class content::FieldTrialSynchronizer;
+ friend class variations::ChildProcessFieldTrialSyncer;
+
+ // This is only used to simulate seed changes, not sent to Google servers.
+ friend variations::SeedSimulationResult variations::ComputeDifferences(
+ const std::vector<variations::ProcessedStudy>& processed_studies,
+ const variations::VariationsLayers& layers,
+ const variations::EntropyProviders& entropy_providers);
+
+ // Include all active field trials in crash reports, so that crashes are
+ // reproducible: https://www.google.com/intl/en/chrome/privacy/.
+ friend class variations::VariationsCrashKeys;
+
+ // This usage is to display field trials in chrome://version and other local
+ // internal UIs.
+ friend base::Value::List version_ui::GetVariationsList();
+
+ // Required for tests.
+ friend class TestFieldTrialObserverIncludingLowAnonymity;
+ FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ObserveIncludingLowAnonymity);
+
+ private:
+ // The same as |FieldTrialList::GetActiveFieldTrialGroups| but gives access to
+ // low anonymity field trials too.
+ static void GetActiveFieldTrialGroups(
+ FieldTrial::ActiveGroups* active_groups);
+
+ // Identical to |FieldTrialList::AddObserver| but also notifies of low
+ // anonymity trials.
+ static bool AddObserver(FieldTrialList::Observer* observer);
+
+ // Identical to |FieldTrialList::RemoveObserver| but for observers registered
+ // through the AddObserver() function of this class.
+ static void RemoveObserver(FieldTrialList::Observer* observer);
+};
+
+} // namespace base
+
+#endif // BASE_METRICS_FIELD_TRIAL_LIST_INCLUDING_LOW_ANONYMITY_H_
diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc
index 269980605..a2a8ef31b 100644
--- a/base/metrics/field_trial_unittest.cc
+++ b/base/metrics/field_trial_unittest.cc
@@ -12,6 +12,7 @@
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
+#include "base/metrics/field_trial_list_including_low_anonymity.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
@@ -25,6 +26,7 @@
#include "base/test/test_shared_memory_util.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
@@ -51,10 +53,12 @@ const char kDefaultGroupName[] = "DefaultGroup";
scoped_refptr<FieldTrial> CreateFieldTrial(
const std::string& trial_name,
int total_probability,
- const std::string& default_group_name) {
+ const std::string& default_group_name,
+ bool is_low_anonymity = false) {
MockEntropyProvider entropy_provider(0.9);
return FieldTrialList::FactoryGetFieldTrial(
- trial_name, total_probability, default_group_name, entropy_provider);
+ trial_name, total_probability, default_group_name, entropy_provider, 0,
+ is_low_anonymity);
}
// A FieldTrialList::Observer implementation which stores the trial name and
@@ -116,6 +120,37 @@ std::string MockEscapeQueryParamValue(const std::string& input) {
} // namespace
+// Same as |TestFieldTrialObserver|, but registers for low anonymity field
+// trials too.
+class TestFieldTrialObserverIncludingLowAnonymity
+ : public FieldTrialList::Observer {
+ public:
+ TestFieldTrialObserverIncludingLowAnonymity() {
+ FieldTrialListIncludingLowAnonymity::AddObserver(this);
+ }
+ TestFieldTrialObserverIncludingLowAnonymity(
+ const TestFieldTrialObserverIncludingLowAnonymity&) = delete;
+ TestFieldTrialObserverIncludingLowAnonymity& operator=(
+ const TestFieldTrialObserverIncludingLowAnonymity&) = delete;
+
+ ~TestFieldTrialObserverIncludingLowAnonymity() override {
+ FieldTrialListIncludingLowAnonymity::RemoveObserver(this);
+ }
+
+ void OnFieldTrialGroupFinalized(const std::string& trial,
+ const std::string& group) override {
+ trial_name_ = trial;
+ group_name_ = group;
+ }
+
+ const std::string& trial_name() const { return trial_name_; }
+ const std::string& group_name() const { return group_name_; }
+
+ private:
+ std::string trial_name_;
+ std::string group_name_;
+};
+
class FieldTrialTest : public ::testing::Test {
public:
FieldTrialTest() {
@@ -131,6 +166,13 @@ class FieldTrialTest : public ::testing::Test {
test::ScopedFeatureList scoped_feature_list_;
};
+MATCHER(CompareActiveGroupToFieldTrial, "") {
+ const base::FieldTrial::ActiveGroup& lhs = ::testing::get<0>(arg);
+ const base::FieldTrial* rhs = ::testing::get<1>(arg).get();
+ return lhs.trial_name == rhs->trial_name() &&
+ lhs.group_name == rhs->group_name_internal();
+}
+
// Test registration, and also check that destructors are called for trials.
TEST_F(FieldTrialTest, Registration) {
const char name1[] = "name 1 test";
@@ -762,8 +804,8 @@ TEST_F(FieldTrialTest, FloatBoundariesGiveEqualGroupSizes) {
for (int i = 0; i < kBucketCount; ++i) {
const double entropy = i / static_cast<double>(kBucketCount);
- scoped_refptr<FieldTrial> trial(
- new FieldTrial("test", kBucketCount, "default", entropy));
+ scoped_refptr<FieldTrial> trial(new FieldTrial(
+ "test", kBucketCount, "default", entropy, /*is_low_anonymity=*/false));
for (int j = 0; j < kBucketCount; ++j)
trial->AppendGroup(NumberToString(j), 1);
@@ -775,8 +817,8 @@ TEST_F(FieldTrialTest, DoesNotSurpassTotalProbability) {
const double kEntropyValue = 1.0 - 1e-9;
ASSERT_LT(kEntropyValue, 1.0);
- scoped_refptr<FieldTrial> trial(
- new FieldTrial("test", 2, "default", kEntropyValue));
+ scoped_refptr<FieldTrial> trial(new FieldTrial(
+ "test", 2, "default", kEntropyValue, /*is_low_anonymity=*/false));
trial->AppendGroup("1", 1);
trial->AppendGroup("2", 1);
@@ -1279,4 +1321,53 @@ TEST_F(FieldTrialTest, TestAllParamsToString) {
FieldTrialList::AllParamsToString(&MockEscapeQueryParamValue));
}
+TEST_F(FieldTrialTest, GetActiveFieldTrialGroups_LowAnonymity) {
+ // Create a field trial with a single winning group.
+ scoped_refptr<FieldTrial> trial_1 = CreateFieldTrial("Normal", 10, "Default");
+ trial_1->AppendGroup("Winner 1", 10);
+ trial_1->Activate();
+
+ // Create a second field trial with a single winning group, marked as
+ // low-anonymity.
+ scoped_refptr<FieldTrial> trial_2 = CreateFieldTrial(
+ "Low anonymity", 10, "Default", /*is_low_anonymity=*/true);
+ trial_2->AppendGroup("Winner 2", 10);
+ trial_2->Activate();
+
+ // Check that |FieldTrialList::GetActiveFieldTrialGroups()| does not include
+ // the low-anonymity trial.
+ FieldTrial::ActiveGroups active_groups_for_metrics;
+ FieldTrialList::GetActiveFieldTrialGroups(&active_groups_for_metrics);
+ EXPECT_THAT(
+ active_groups_for_metrics,
+ testing::UnorderedPointwise(CompareActiveGroupToFieldTrial(), {trial_1}));
+
+ // Check that
+ // |FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups()| includes
+ // both trials.
+ FieldTrial::ActiveGroups active_groups;
+ FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroupsForTesting(
+ &active_groups);
+ EXPECT_THAT(active_groups,
+ testing::UnorderedPointwise(CompareActiveGroupToFieldTrial(),
+ {trial_1, trial_2}));
+}
+
+TEST_F(FieldTrialTest, ObserveIncludingLowAnonymity) {
+ TestFieldTrialObserver observer;
+ TestFieldTrialObserverIncludingLowAnonymity low_anonymity_observer;
+
+ // Create a low-anonymity trial with one active group.
+ const char kTrialName[] = "TrialToObserve1";
+ scoped_refptr<FieldTrial> trial = CreateFieldTrial(
+ kTrialName, 100, kDefaultGroupName, /*is_low_anonymity=*/true);
+ trial->Activate();
+
+ // Only the low_anonymity_observer should be notified.
+ EXPECT_EQ("", observer.trial_name());
+ EXPECT_EQ("", observer.group_name());
+ EXPECT_EQ(kTrialName, low_anonymity_observer.trial_name());
+ EXPECT_EQ(kDefaultGroupName, low_anonymity_observer.group_name());
+}
+
} // namespace base
diff --git a/base/metrics/histogram_threadsafe_unittest.cc b/base/metrics/histogram_threadsafe_unittest.cc
index 753e9c835..aeb0e8556 100644
--- a/base/metrics/histogram_threadsafe_unittest.cc
+++ b/base/metrics/histogram_threadsafe_unittest.cc
@@ -181,6 +181,8 @@ class HistogramThreadsafeTest : public testing::Test {
}
void TearDown() override {
+ histograms_.clear();
+ allocator_view_.reset();
GlobalHistogramAllocator::ReleaseForTesting();
ASSERT_FALSE(GlobalHistogramAllocator::Get());
}
@@ -297,7 +299,7 @@ class HistogramThreadsafeTest : public testing::Test {
// use of ASSERT_* instead EXPECT_* because the test is repeated multiple times,
// and the use of EXPECT_* produces spammy outputs as it does not end the test
// immediately.
-TEST_F(HistogramThreadsafeTest, SnapshotDeltaThreadsafe) {
+TEST_F(HistogramThreadsafeTest, DISABLED_SnapshotDeltaThreadsafe) {
// We try this test |kNumIterations| times to have a coverage of different
// scenarios. For example, for a numeric histogram, if it has only samples
// within the same bucket, the samples will be stored in a different way than
@@ -332,11 +334,11 @@ TEST_F(HistogramThreadsafeTest, SnapshotDeltaThreadsafe) {
// later on.
constexpr size_t kNumThreads = 2;
constexpr size_t kNumEmissions = 2000;
- std::unique_ptr<SnapshotDeltaThread> threads[kNumThreads];
subtle::Atomic32 real_total_samples_count = 0;
std::vector<subtle::Atomic32> real_bucket_counts(kHistogramMax, 0);
subtle::Atomic32 snapshots_total_samples_count = 0;
std::vector<subtle::Atomic32> snapshots_bucket_counts(kHistogramMax, 0);
+ std::unique_ptr<SnapshotDeltaThread> threads[kNumThreads];
for (size_t i = 0; i < kNumThreads; ++i) {
threads[i] = std::make_unique<SnapshotDeltaThread>(
StringPrintf("SnapshotDeltaThread.%zu.%zu", iteration, i),
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index d29e21710..ec0e85287 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -10,6 +10,7 @@
#include "base/bits.h"
#include "base/debug/alias.h"
+#include "base/debug/crash_logging.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
@@ -17,6 +18,7 @@
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
+#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/system/sys_info.h"
#include "base/threading/scoped_blocking_call.h"
@@ -29,6 +31,9 @@
#include <winbase.h>
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include <sys/mman.h>
+#if BUILDFLAG(IS_ANDROID)
+#include <sys/prctl.h>
+#endif
#endif
namespace {
@@ -959,8 +964,12 @@ LocalPersistentMemoryAllocator::LocalPersistentMemoryAllocator(
size_t size,
uint64_t id,
base::StringPiece name)
- : PersistentMemoryAllocator(AllocateLocalMemory(size),
- size, 0, id, name, false) {}
+ : PersistentMemoryAllocator(AllocateLocalMemory(size, name),
+ size,
+ 0,
+ id,
+ name,
+ false) {}
LocalPersistentMemoryAllocator::~LocalPersistentMemoryAllocator() {
DeallocateLocalMemory(const_cast<char*>(mem_base_), mem_size_, mem_type_);
@@ -968,7 +977,8 @@ LocalPersistentMemoryAllocator::~LocalPersistentMemoryAllocator() {
// static
PersistentMemoryAllocator::Memory
-LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size) {
+LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size,
+ base::StringPiece name) {
void* address;
#if BUILDFLAG(IS_WIN)
@@ -981,8 +991,16 @@ LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size) {
// MAP_SHARED is not available on Linux <2.4 but required on Mac.
address = ::mmap(nullptr, size, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_SHARED, -1, 0);
- if (address != MAP_FAILED)
+ if (address != MAP_FAILED) {
+#if BUILDFLAG(IS_ANDROID)
+ // Allow the anonymous memory region allocated by mmap(MAP_ANON) to be
+ // identified in /proc/$PID/smaps. This helps improve visibility into
+ // Chrome's memory usage on Android.
+ const std::string arena_name = base::StrCat({"persistent:", name});
+ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, address, size, arena_name.c_str());
+#endif
return Memory(address, MEM_VIRTUAL);
+ }
#else
#error This architecture is not (yet) supported.
#endif
@@ -1177,6 +1195,14 @@ void* DelayedPersistentAllocation::Get() const {
// Relaxed operations are acceptable here because it's not protecting the
// contents of the allocation in any way.
Reference ref = reference_->load(std::memory_order_acquire);
+
+#if !BUILDFLAG(IS_NACL)
+ // TODO(crbug/1432981): Remove these. They are used to investigate unexpected
+ // failures.
+ bool ref_found = (ref != 0);
+ bool raced = false;
+#endif // !BUILDFLAG(IS_NACL)
+
if (!ref) {
ref = allocator_->Allocate(size_, type_);
if (!ref)
@@ -1196,11 +1222,27 @@ void* DelayedPersistentAllocation::Get() const {
DCHECK_LE(size_, allocator_->GetAllocSize(existing));
allocator_->ChangeType(ref, 0, type_, /*clear=*/false);
ref = existing;
+#if !BUILDFLAG(IS_NACL)
+ raced = true;
+#endif // !BUILDFLAG(IS_NACL)
}
}
char* mem = allocator_->GetAsArray<char>(ref, type_, size_);
if (!mem) {
+#if !BUILDFLAG(IS_NACL)
+ // TODO(crbug/1432981): Remove these. They are used to investigate
+ // unexpected failures.
+ SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "full",
+ allocator_->IsFull());
+ SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "corrupted",
+ allocator_->IsCorrupt());
+ SCOPED_CRASH_KEY_NUMBER("PersistentMemoryAllocator", "ref", ref);
+ SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "ref_found", ref_found);
+ SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "raced", raced);
+ SCOPED_CRASH_KEY_NUMBER("PersistentMemoryAllocator", "type_", type_);
+ SCOPED_CRASH_KEY_NUMBER("PersistentMemoryAllocator", "size_", size_);
+#endif // !BUILDFLAG(IS_NACL)
// This should never happen but be tolerant if it does as corruption from
// the outside is something to guard against.
NOTREACHED();
diff --git a/base/metrics/persistent_memory_allocator.h b/base/metrics/persistent_memory_allocator.h
index e5ef0ffa3..806df91c3 100644
--- a/base/metrics/persistent_memory_allocator.h
+++ b/base/metrics/persistent_memory_allocator.h
@@ -737,7 +737,7 @@ class BASE_EXPORT LocalPersistentMemoryAllocator
// Allocates a block of local memory of the specified |size|, ensuring that
// the memory will not be physically allocated until accessed and will read
// as zero when that happens.
- static Memory AllocateLocalMemory(size_t size);
+ static Memory AllocateLocalMemory(size_t size, base::StringPiece name);
// Deallocates a block of local |memory| of the specified |size|.
static void DeallocateLocalMemory(void* memory, size_t size, MemoryType type);
diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc
index 52bab7cec..de7512207 100644
--- a/base/metrics/persistent_sample_map.cc
+++ b/base/metrics/persistent_sample_map.cc
@@ -7,6 +7,7 @@
#include "base/atomicops.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
+#include "base/debug/crash_logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/notreached.h"
@@ -193,6 +194,13 @@ PersistentSampleMap::CreatePersistentRecord(
Sample value) {
SampleRecord* record = allocator->New<SampleRecord>();
if (!record) {
+#if !BUILDFLAG(IS_NACL)
+ // TODO(crbug/1432981): Remove these. They are used to investigate
+ // unexpected failures.
+ SCOPED_CRASH_KEY_BOOL("PersistentSampleMap", "full", allocator->IsFull());
+ SCOPED_CRASH_KEY_BOOL("PersistentSampleMap", "corrupted",
+ allocator->IsCorrupt());
+#endif // !BUILDFLAG(IS_NACL)
NOTREACHED() << "full=" << allocator->IsFull()
<< ", corrupt=" << allocator->IsCorrupt();
return 0;
diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc
index e992edec0..0192748a5 100644
--- a/base/metrics/sample_vector.cc
+++ b/base/metrics/sample_vector.cc
@@ -7,6 +7,7 @@
#include <ostream>
#include "base/check_op.h"
+#include "base/debug/crash_logging.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/persistent_memory_allocator.h"
@@ -324,6 +325,16 @@ bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
// Ensure that the sample's min/max match the ranges min/max.
if (min != bucket_ranges_->range(dest_index) ||
max != bucket_ranges_->range(dest_index + 1)) {
+#if !BUILDFLAG(IS_NACL)
+ // TODO(crbug/1432981): Remove these. They are used to investigate
+ // unexpected failures.
+ SCOPED_CRASH_KEY_NUMBER("SampleVector", "min", min);
+ SCOPED_CRASH_KEY_NUMBER("SampleVector", "max", max);
+ SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_min",
+ bucket_ranges_->range(dest_index));
+ SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_max",
+ bucket_ranges_->range(dest_index + 1));
+#endif // !BUILDFLAG(IS_NACL)
NOTREACHED() << "sample=" << min << "," << max
<< "; range=" << bucket_ranges_->range(dest_index) << ","
<< bucket_ranges_->range(dest_index + 1);
diff --git a/base/nix/xdg_util.cc b/base/nix/xdg_util.cc
index fd4076d06..06ebf54b6 100644
--- a/base/nix/xdg_util.cc
+++ b/base/nix/xdg_util.cc
@@ -86,6 +86,9 @@ DesktopEnvironment GetDesktopEnvironment(Environment* env) {
if (kde_session == "5") {
return DESKTOP_ENVIRONMENT_KDE5;
}
+ if (kde_session == "6") {
+ return DESKTOP_ENVIRONMENT_KDE6;
+ }
}
return DESKTOP_ENVIRONMENT_KDE4;
}
@@ -152,6 +155,8 @@ const char* GetDesktopEnvironmentName(DesktopEnvironment env) {
return "KDE4";
case DESKTOP_ENVIRONMENT_KDE5:
return "KDE5";
+ case DESKTOP_ENVIRONMENT_KDE6:
+ return "KDE6";
case DESKTOP_ENVIRONMENT_PANTHEON:
return "PANTHEON";
case DESKTOP_ENVIRONMENT_UNITY:
diff --git a/base/nix/xdg_util.h b/base/nix/xdg_util.h
index 4d26721d7..7085e152e 100644
--- a/base/nix/xdg_util.h
+++ b/base/nix/xdg_util.h
@@ -22,11 +22,12 @@ enum DesktopEnvironment {
DESKTOP_ENVIRONMENT_CINNAMON = 1,
DESKTOP_ENVIRONMENT_DEEPIN = 2,
DESKTOP_ENVIRONMENT_GNOME = 3,
- // KDE3, KDE4 and KDE5 are sufficiently different that we count
+ // KDE{3,4,5,6} are sufficiently different that we count
// them as different desktop environments here.
DESKTOP_ENVIRONMENT_KDE3 = 4,
DESKTOP_ENVIRONMENT_KDE4 = 5,
DESKTOP_ENVIRONMENT_KDE5 = 6,
+ DESKTOP_ENVIRONMENT_KDE6 = 12,
DESKTOP_ENVIRONMENT_PANTHEON = 7,
DESKTOP_ENVIRONMENT_UKUI = 8,
DESKTOP_ENVIRONMENT_UNITY = 9,
diff --git a/base/nix/xdg_util_unittest.cc b/base/nix/xdg_util_unittest.cc
index bd028681f..19f8aad46 100644
--- a/base/nix/xdg_util_unittest.cc
+++ b/base/nix/xdg_util_unittest.cc
@@ -44,6 +44,7 @@ const char* const kXdgDesktopUnity = "Unity";
const char* const kXdgDesktopUnity7 = "Unity:Unity7";
const char* const kXdgDesktopUnity8 = "Unity:Unity8";
const char* const kKDESessionKDE5 = "5";
+const char* const kKDESessionKDE6 = "6";
const char kDesktopSession[] = "DESKTOP_SESSION";
const char kKDESession[] = "KDE_SESSION_VERSION";
@@ -162,6 +163,17 @@ TEST(XDGUtilTest, GetXdgDesktopKDE5) {
EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE5, GetDesktopEnvironment(&getter));
}
+TEST(XDGUtilTest, GetXdgDesktopKDE6) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgCurrentDesktopEnvVar), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopKDE), Return(true)));
+ EXPECT_CALL(getter, GetVar(Eq(kKDESession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kKDESessionKDE6), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE6, GetDesktopEnvironment(&getter));
+}
+
TEST(XDGUtilTest, GetXdgDesktopKDE4) {
MockEnvironment getter;
EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
diff --git a/base/process/process_metrics.cc b/base/process/process_metrics.cc
index 1b88164d3..a3b441d79 100644
--- a/base/process/process_metrics.cc
+++ b/base/process/process_metrics.cc
@@ -90,6 +90,8 @@ Value::Dict SystemMetrics::ToDict() const {
return res;
}
+ProcessMetrics::~ProcessMetrics() = default;
+
std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateCurrentProcessMetrics() {
#if !BUILDFLAG(IS_MAC)
return CreateProcessMetrics(base::GetCurrentProcessHandle());
diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h
index 79d1238d5..4e2541958 100644
--- a/base/process/process_metrics.h
+++ b/base/process/process_metrics.h
@@ -605,7 +605,7 @@ class BASE_EXPORT SystemMetrics {
#endif
};
-#if BUILDFLAG(IS_MAC)
+#if BUILDFLAG(IS_APPLE)
enum class MachVMRegionResult {
// There were no more memory regions between |address| and the end of the
// virtual address space.
@@ -619,17 +619,6 @@ enum class MachVMRegionResult {
};
// Returns info on the first memory region at or after |address|, including
-// resident memory and share mode. On Success, |size| reflects the size of the
-// memory region.
-// |size| and |info| are output parameters, only valid on Success.
-// |address| is an in-out parameter, than represents both the address to start
-// looking, and the start address of the memory region.
-BASE_EXPORT MachVMRegionResult GetTopInfo(mach_port_t task,
- mach_vm_size_t* size,
- mach_vm_address_t* address,
- vm_region_top_info_data_t* info);
-
-// Returns info on the first memory region at or after |address|, including
// protection values. On Success, |size| reflects the size of the
// memory region.
// Returns info on the first memory region at or after |address|, including
@@ -639,6 +628,19 @@ BASE_EXPORT MachVMRegionResult GetBasicInfo(mach_port_t task,
mach_vm_size_t* size,
mach_vm_address_t* address,
vm_region_basic_info_64* info);
+#endif // BUILDFLAG(IS_APPLE)
+
+#if BUILDFLAG(IS_MAC)
+// Returns info on the first memory region at or after |address|, including
+// resident memory and share mode. On Success, |size| reflects the size of the
+// memory region.
+// |size| and |info| are output parameters, only valid on Success.
+// |address| is an in-out parameter, than represents both the address to start
+// looking, and the start address of the memory region.
+BASE_EXPORT MachVMRegionResult GetTopInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_top_info_data_t* info);
#endif // BUILDFLAG(IS_MAC)
} // namespace base
diff --git a/base/process/process_metrics_ios.cc b/base/process/process_metrics_ios.cc
index 685cc2658..a5571e899 100644
--- a/base/process/process_metrics_ios.cc
+++ b/base/process/process_metrics_ios.cc
@@ -6,6 +6,7 @@
#include <limits.h>
#include <mach/task.h>
+#include <mach/vm_region.h>
#include <malloc/malloc.h>
#include <stddef.h>
@@ -14,13 +15,12 @@
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
+#include "build/blink_buildflags.h"
namespace base {
ProcessMetrics::ProcessMetrics(ProcessHandle process) {}
-ProcessMetrics::~ProcessMetrics() {}
-
// static
std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
ProcessHandle process) {
@@ -32,6 +32,10 @@ TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
return TimeDelta();
}
+// The blink code path pulls in process_metrics_posix.cc which
+// is used for the following implementations.
+#if !BUILDFLAG(USE_BLINK)
+
size_t GetMaxFds() {
static const rlim_t kSystemDefaultMaxFds = 256;
rlim_t max_fds;
@@ -53,6 +57,13 @@ void IncreaseFdLimitTo(unsigned int max_descriptors) {
// Unimplemented.
}
+size_t ProcessMetrics::GetMallocUsage() {
+ malloc_statistics_t stats;
+ malloc_zone_statistics(nullptr, &stats);
+ return stats.size_in_use;
+}
+#endif // !BUILDFLAG(USE_BLINK)
+
// Bytes committed by the system.
size_t GetSystemCommitCharge() {
NOTIMPLEMENTED();
@@ -95,10 +106,28 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
return true;
}
-size_t ProcessMetrics::GetMallocUsage() {
- malloc_statistics_t stats;
- malloc_zone_statistics(nullptr, &stats);
- return stats.size_in_use;
+MachVMRegionResult ParseOutputFromVMRegion(kern_return_t kr) {
+ if (kr == KERN_INVALID_ADDRESS) {
+ // We're at the end of the address space.
+ return MachVMRegionResult::Finished;
+ } else if (kr != KERN_SUCCESS) {
+ return MachVMRegionResult::Error;
+ }
+ return MachVMRegionResult::Success;
+}
+
+MachVMRegionResult GetBasicInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_basic_info_64* info) {
+ mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
+ mac::ScopedMachSendRight object_name;
+ kern_return_t kr =
+ vm_region_64(task, reinterpret_cast<vm_address_t*>(address),
+ reinterpret_cast<vm_size_t*>(size), VM_REGION_BASIC_INFO_64,
+ reinterpret_cast<vm_region_info_t>(info), &info_count,
+ mac::ScopedMachSendRight::Receiver(object_name).get());
+ return ParseOutputFromVMRegion(kr);
}
} // namespace base
diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc
index 873a328aa..9cb9d92bb 100644
--- a/base/process/process_metrics_posix.cc
+++ b/base/process/process_metrics_posix.cc
@@ -39,8 +39,6 @@ int64_t TimeValToMicroseconds(const struct timeval& tv) {
return ret;
}
-ProcessMetrics::~ProcessMetrics() = default;
-
#if !BUILDFLAG(IS_FUCHSIA)
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
diff --git a/base/process/process_metrics_win.cc b/base/process/process_metrics_win.cc
index 41bd51e39..e8987bcee 100644
--- a/base/process/process_metrics_win.cc
+++ b/base/process/process_metrics_win.cc
@@ -122,8 +122,6 @@ struct SYSTEM_PERFORMANCE_INFORMATION {
} // namespace
-ProcessMetrics::~ProcessMetrics() { }
-
size_t GetMaxFds() {
// Windows is only limited by the amount of physical memory.
return std::numeric_limits<size_t>::max();
diff --git a/base/profiler/native_unwinder_android_unittest.cc b/base/profiler/native_unwinder_android_unittest.cc
index 3fe4adf26..29124ae14 100644
--- a/base/profiler/native_unwinder_android_unittest.cc
+++ b/base/profiler/native_unwinder_android_unittest.cc
@@ -16,6 +16,7 @@
#include "base/android/build_info.h"
#include "base/android/jni_android.h"
#include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
#include "base/profiler/native_unwinder_android_map_delegate.h"
#include "base/profiler/native_unwinder_android_memory_regions_map.h"
#include "base/profiler/register_context.h"
@@ -65,8 +66,8 @@ class NativeUnwinderAndroidMemoryRegionsMapForTesting
std::unique_ptr<unwindstack::Memory> TakeMemory() override { return nullptr; }
private:
- unwindstack::Maps* maps_;
- unwindstack::Memory* memory_;
+ raw_ptr<unwindstack::Maps> maps_;
+ raw_ptr<unwindstack::Memory> memory_;
};
class NativeUnwinderAndroidMapDelegateForTesting
diff --git a/base/profiler/stack_sampler.cc b/base/profiler/stack_sampler.cc
index 65d3eb9d1..b1659b1f8 100644
--- a/base/profiler/stack_sampler.cc
+++ b/base/profiler/stack_sampler.cc
@@ -150,8 +150,15 @@ void StackSampler::RecordStackFrames(StackBuffer* stack_buffer,
if ((++stack_size_histogram_sampling_counter_ %
kUMAHistogramDownsampleAmount) == 0) {
// Record the size of the stack to tune kLargeStackSize.
- UmaHistogramMemoryKB("Memory.StackSamplingProfiler.StackSampleSize",
- saturated_cast<int>(stack_size / kBytesPerKilobyte));
+ // UmaHistogramMemoryKB has a min of 1000, which isn't useful for our
+ // purposes, so call UmaHistogramCustomCounts directly.
+ // Min is 4KB, since that's the normal pagesize and setting kLargeStackSize
+ // smaller than that would be pointless. Max is 8MB since that's the
+ // current ChromeOS stack size; we shouldn't be able to get a number
+ // larger than that.
+ UmaHistogramCustomCounts(
+ "Memory.StackSamplingProfiler.StackSampleSize2",
+ saturated_cast<int>(stack_size / kBytesPerKilobyte), 4, 8 * 1024, 50);
}
// We expect to very rarely see stacks larger than kLargeStackSize. If we see
diff --git a/base/profiler/stack_sampler.h b/base/profiler/stack_sampler.h
index 962245392..d6154ce16 100644
--- a/base/profiler/stack_sampler.h
+++ b/base/profiler/stack_sampler.h
@@ -94,7 +94,7 @@ class BASE_EXPORT StackSampler {
StackSamplerTestDelegate* test_delegate = nullptr);
#if BUILDFLAG(IS_CHROMEOS)
- // How often to record the "Memory.StackSamplingProfiler.StackSampleSize" UMA
+ // How often to record the "Memory.StackSamplingProfiler.StackSampleSize2" UMA
// histogram. Specifically, only 1 in kUMAHistogramDownsampleAmount calls to
// RecordStackFrames will add a sample to the histogram. RecordStackFrames is
// called many times a second. We don't need multiple samples per second to
@@ -130,7 +130,7 @@ class BASE_EXPORT StackSampler {
const raw_ptr<StackSamplerTestDelegate> test_delegate_;
#if BUILDFLAG(IS_CHROMEOS)
- // Counter for "Memory.StackSamplingProfiler.StackSampleSize" UMA histogram.
+ // Counter for "Memory.StackSamplingProfiler.StackSampleSize2" UMA histogram.
// See comments above kUMAHistogramDownsampleAmount. Unsigned so that overflow
// isn't undefined behavior.
uint32_t stack_size_histogram_sampling_counter_ = 0;
diff --git a/base/profiler/stack_sampler_unittest.cc b/base/profiler/stack_sampler_unittest.cc
index 41d593f3c..c5b9b5fc7 100644
--- a/base/profiler/stack_sampler_unittest.cc
+++ b/base/profiler/stack_sampler_unittest.cc
@@ -319,16 +319,16 @@ TEST(StackSamplerTest, RecordStackFramesUMAMetric) {
PlatformThread::CurrentId());
// Should have no new samples in the
- // Memory.StackSamplingProfiler.StackSampleSize histogram.
+ // Memory.StackSamplingProfiler.StackSampleSize2 histogram.
histogram_tester.ExpectUniqueSample(
- "Memory.StackSamplingProfiler.StackSampleSize", kExpectedSizeKB, 0);
+ "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 0);
}
stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
PlatformThread::CurrentId());
histogram_tester.ExpectUniqueSample(
- "Memory.StackSamplingProfiler.StackSampleSize", kExpectedSizeKB, 1);
+ "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 1);
}
#endif // #if BUILDFLAG(IS_CHROMEOS)
diff --git a/base/strings/DEPS b/base/strings/DEPS
deleted file mode 100644
index db5863570..000000000
--- a/base/strings/DEPS
+++ /dev/null
@@ -1,8 +0,0 @@
-specific_include_rules = {
- # absl::string_view is generally banned in Chromium. Including it is only
- # allowed from abseil_string_conversions* to provide appropriate conversion
- # functions from and to StringPiece at third party API boundaries.
- "abseil_string_conversions(\.h|\.cc|_unittest.cc)": [
- "+third_party/abseil-cpp/absl/strings/string_view.h",
- ],
-}
diff --git a/base/strings/abseil_string_conversions.cc b/base/strings/abseil_string_conversions.cc
deleted file mode 100644
index 9606ee2f6..000000000
--- a/base/strings/abseil_string_conversions.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/strings/abseil_string_conversions.h"
-
-#include <vector>
-
-#include "base/containers/span.h"
-#include "base/ranges/algorithm.h"
-#include "base/strings/string_piece.h"
-#include "third_party/abseil-cpp/absl/strings/string_view.h"
-
-namespace base {
-
-std::vector<absl::string_view> StringPiecesToStringViews(
- span<const StringPiece> pieces) {
- std::vector<absl::string_view> views(pieces.size());
- ranges::transform(pieces, views.begin(), &StringPieceToStringView);
- return views;
-}
-
-std::vector<StringPiece> StringViewsToStringPieces(
- span<const absl::string_view> views) {
- std::vector<StringPiece> pieces(views.size());
- ranges::transform(views, pieces.begin(), &StringViewToStringPiece);
- return pieces;
-}
-
-} // namespace base
diff --git a/base/strings/abseil_string_conversions.h b/base/strings/abseil_string_conversions.h
deleted file mode 100644
index 5b8e045d5..000000000
--- a/base/strings/abseil_string_conversions.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_STRINGS_ABSEIL_STRING_CONVERSIONS_H_
-#define BASE_STRINGS_ABSEIL_STRING_CONVERSIONS_H_
-
-#include <vector>
-
-#include "base/base_export.h"
-#include "base/containers/span.h"
-#include "base/strings/string_piece.h"
-#include "third_party/abseil-cpp/absl/strings/string_view.h"
-
-namespace base {
-
-// Converts `piece` to a string view, pointing to the same piece of memory.
-constexpr absl::string_view StringPieceToStringView(StringPiece piece) {
- return {piece.data(), piece.size()};
-}
-
-// Converts `view` to a string piece, pointing to the same piece of memory.
-constexpr StringPiece StringViewToStringPiece(absl::string_view view) {
- return {view.data(), view.size()};
-}
-
-// Converts `pieces` to string views, pointing to the same piece of memory.
-BASE_EXPORT std::vector<absl::string_view> StringPiecesToStringViews(
- span<const StringPiece> pieces);
-
-// Converts `views` to string pieces, pointing to the same piece of memory.
-BASE_EXPORT std::vector<StringPiece> StringViewsToStringPieces(
- span<const absl::string_view> views);
-
-} // namespace base
-
-#endif // BASE_STRINGS_ABSEIL_STRING_CONVERSIONS_H_
diff --git a/base/strings/abseil_string_conversions_unittest.cc b/base/strings/abseil_string_conversions_unittest.cc
deleted file mode 100644
index 106d1a62b..000000000
--- a/base/strings/abseil_string_conversions_unittest.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/strings/abseil_string_conversions.h"
-
-#include <vector>
-
-#include "base/containers/span.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_piece_forward.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/strings/string_view.h"
-
-namespace base {
-
-namespace {
-using string_view = absl::string_view;
-} // namespace
-
-TEST(AbseilStringConversionsTest, StringPieceToStringView) {
- static constexpr StringPiece kPiece = "foo";
- static constexpr string_view kView = StringPieceToStringView(kPiece);
- static_assert(kPiece.data() == kView.data(), "");
- static_assert(kPiece.size() == kView.size(), "");
-}
-
-TEST(AbseilStringConversionsTest, StringViewToStringPiece) {
- static constexpr string_view kView = "bar";
- static constexpr StringPiece kPiece = StringViewToStringPiece(kView);
- static_assert(kView.data() == kPiece.data(), "");
- static_assert(kView.size() == kPiece.size(), "");
-}
-
-TEST(AbseilStringConversionsTest, StringPiecesToStringViews) {
- static constexpr StringPiece kFoo = "foo";
- static constexpr StringPiece kBar = "bar";
- static constexpr StringPiece kBaz = "baz";
-
- const std::vector<StringPiece> kPieces = {kFoo, kBar, kBaz};
- const std::vector<string_view> kViews = StringPiecesToStringViews(kPieces);
-
- ASSERT_EQ(kViews.size(), 3u);
- EXPECT_EQ(kViews[0].data(), kFoo);
- EXPECT_EQ(kViews[0].size(), 3u);
- EXPECT_EQ(kViews[1].data(), kBar);
- EXPECT_EQ(kViews[1].size(), 3u);
- EXPECT_EQ(kViews[2].data(), kBaz);
- EXPECT_EQ(kViews[2].size(), 3u);
-}
-
-TEST(AbseilStringConversionsTest, StringViewsToStringPieces) {
- static constexpr string_view kFoo = "foo";
- static constexpr string_view kBar = "bar";
- static constexpr string_view kBaz = "baz";
-
- const std::vector<string_view> kViews = {kFoo, kBar, kBaz};
- const std::vector<StringPiece> kPieces = StringViewsToStringPieces(kViews);
-
- ASSERT_EQ(kPieces.size(), 3u);
- EXPECT_EQ(kPieces[0].data(), kFoo);
- EXPECT_EQ(kPieces[0].size(), 3u);
- EXPECT_EQ(kPieces[1].data(), kBar);
- EXPECT_EQ(kPieces[1].size(), 3u);
- EXPECT_EQ(kPieces[2].data(), kBaz);
- EXPECT_EQ(kPieces[2].size(), 3u);
-}
-
-} // namespace base
diff --git a/base/strings/escape.cc b/base/strings/escape.cc
index d76bb6e2a..d303ead90 100644
--- a/base/strings/escape.cc
+++ b/base/strings/escape.cc
@@ -543,9 +543,20 @@ std::string UnescapeBinaryURLComponent(StringPiece escaped_text,
DCHECK(!(rules &
~(UnescapeRule::NORMAL | UnescapeRule::REPLACE_PLUS_WITH_SPACE)));
+ // It is not possible to read the feature state when this function is invoked
+ // before FeatureList initialization. In that case, fallback to the feature's
+ // default state.
+ //
+ // TODO(crbug.com/1321924): Cleanup this feature.
+ const bool optimize_data_urls_feature_is_enabled =
+ base::FeatureList::GetInstance()
+ ? base::FeatureList::IsEnabled(features::kOptimizeDataUrls)
+ : features::kOptimizeDataUrls.default_state ==
+ base::FEATURE_ENABLED_BY_DEFAULT;
+
// If there are no '%' characters in the string, there will be nothing to
// unescape, so we can take the fast path.
- if (base::FeatureList::IsEnabled(features::kOptimizeDataUrls) &&
+ if (optimize_data_urls_feature_is_enabled &&
escaped_text.find('%') == StringPiece::npos) {
std::string unescaped_text(escaped_text);
if (rules & UnescapeRule::REPLACE_PLUS_WITH_SPACE)
diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h
index 9dde6ec29..61273898b 100644
--- a/base/strings/string_piece.h
+++ b/base/strings/string_piece.h
@@ -28,6 +28,7 @@
#include <iosfwd>
#include <limits>
#include <string>
+#include <string_view>
#include <type_traits>
#include "base/base_export.h"
@@ -138,6 +139,7 @@ class GSL_POINTER BasicStringPiece {
// `BasicStringPiece(nullptr_t) = delete`, but unfortunately the terse form is
// not supported by the PNaCl toolchain.
template <class T, class = std::enable_if_t<std::is_null_pointer<T>::value>>
+ // NOLINTNEXTLINE(google-explicit-constructor)
BasicStringPiece(T) {
static_assert(sizeof(T) == 0, // Always false.
"StringPiece does not support construction from nullptr, use "
@@ -148,12 +150,25 @@ class GSL_POINTER BasicStringPiece {
// (an object convertible to) a std::basic_string_view, as well as an explicit
// cast operator to a std::basic_string_view, but (obviously) not from/to a
// BasicStringPiece.
+ // NOLINTNEXTLINE(google-explicit-constructor)
BasicStringPiece(const std::basic_string<CharT>& str)
: ptr_(str.data()), length_(str.size()) {}
explicit operator std::basic_string<CharT>() const {
return std::basic_string<CharT>(data(), size());
}
+ // Provide implicit conversions from/to the STL version, for interoperability
+ // with non-Chromium code.
+ // TODO(crbug.com/691162): These will be moot when BasicStringPiece is
+ // replaced with std::basic_string_view.
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ constexpr BasicStringPiece(std::basic_string_view<CharT> str)
+ : ptr_(str.data()), length_(str.size()) {}
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ constexpr operator std::basic_string_view<CharT>() const {
+ return std::basic_string_view<CharT>(data(), size());
+ }
+
constexpr const_iterator begin() const noexcept { return ptr_; }
constexpr const_iterator cbegin() const noexcept { return ptr_; }
constexpr const_iterator end() const noexcept { return ptr_ + length_; }
diff --git a/base/strings/string_piece_unittest.cc b/base/strings/string_piece_unittest.cc
index cebb31962..9baf7ad30 100644
--- a/base/strings/string_piece_unittest.cc
+++ b/base/strings/string_piece_unittest.cc
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <string>
+#include <string_view>
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
@@ -893,4 +894,19 @@ TEST(StringPieceTest, Find) {
static_assert(foobar.find_last_not_of("ox", 2) == 0, "");
}
+// Test that `base::StringPiece` and `std::string_view` are interoperable.
+TEST(StringPieceTest, StringPieceToStringView) {
+ constexpr StringPiece kPiece = "foo";
+ constexpr std::string_view kView = kPiece;
+ static_assert(kPiece.data() == kView.data());
+ static_assert(kPiece.size() == kView.size());
+}
+
+TEST(StringPieceTest, StringViewToStringPiece) {
+ constexpr std::string_view kView = "bar";
+ constexpr StringPiece kPiece = kView;
+ static_assert(kView.data() == kPiece.data());
+ static_assert(kView.size() == kPiece.size());
+}
+
} // namespace base
diff --git a/base/strings/string_util.h b/base/strings/string_util.h
index 120ffb20b..0367af552 100644
--- a/base/strings/string_util.h
+++ b/base/strings/string_util.h
@@ -13,8 +13,6 @@
#include <stdint.h>
#include <initializer_list>
-#include <memory>
-#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
@@ -26,7 +24,6 @@
#include "base/cxx20_to_address.h"
#include "base/strings/string_piece.h" // For implicit conversions.
#include "base/strings/string_util_internal.h"
-#include "base/template_util.h"
#include "build/build_config.h"
namespace base {
@@ -116,93 +113,6 @@ constexpr WStringPiece MakeWStringPiece(Iter begin, Iter end) {
return MakeBasicStringPiece<wchar_t>(begin, end);
}
-// Convert a type with defined `operator<<` or `.ToString()` method into a
-// string.
-
-// I/O manipulators are function pointers, but should be sent directly to the
-// `ostream` instead of being cast to `const void*` like other function
-// pointers.
-template <typename T, typename = void>
-constexpr bool IsIomanip = false;
-template <typename T>
-constexpr bool
- IsIomanip<T&(T&), std::enable_if_t<std::is_base_of_v<std::ios_base, T>>> =
- true;
-
-// Function pointers implicitly convert to `bool`, so use this to avoid printing
-// function pointers as 1 or 0.
-template <typename T, typename = void>
-constexpr bool WillBeIncorrectlyStreamedAsBool = false;
-template <typename T>
-constexpr bool WillBeIncorrectlyStreamedAsBool<
- T,
- std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<T>> &&
- !IsIomanip<typename std::remove_pointer_t<T>>>> = true;
-
-// Fallback case when there is no better representation.
-template <typename T, typename = void>
-struct ToStringHelper {
- static void Stringify(const T& v, std::ostringstream& ss) {
- ss << "[" << sizeof(v) << "-byte object at 0x" << std::addressof(v) << "]";
- }
-};
-
-// Most streamables.
-template <typename T>
-struct ToStringHelper<
- T,
- std::enable_if_t<base::internal::SupportsOstreamOperator<const T&>::value &&
- !WillBeIncorrectlyStreamedAsBool<T>>> {
- static void Stringify(const T& v, std::ostringstream& ss) { ss << v; }
-};
-
-// Functions and function pointers.
-template <typename T>
-struct ToStringHelper<
- T,
- std::enable_if_t<base::internal::SupportsOstreamOperator<const T&>::value &&
- WillBeIncorrectlyStreamedAsBool<T>>> {
- static void Stringify(const T& v, std::ostringstream& ss) {
- ToStringHelper<const void*>::Stringify(reinterpret_cast<const void*>(v),
- ss);
- }
-};
-
-// Non-streamables that have a `ToString` member.
-template <typename T>
-struct ToStringHelper<
- T,
- std::enable_if_t<
- !base::internal::SupportsOstreamOperator<const T&>::value &&
- base::internal::SupportsToString<const T&>::value>> {
- static void Stringify(const T& v, std::ostringstream& ss) {
- // .ToString() may not return a std::string, e.g. blink::WTF::String.
- ToStringHelper<decltype(v.ToString())>::Stringify(v.ToString(), ss);
- }
-};
-
-// Non-streamable enums (i.e. scoped enums where no `operator<<` overload was
-// declared).
-template <typename T>
-struct ToStringHelper<T,
- std::enable_if_t<!base::internal::SupportsOstreamOperator<
- const T&>::value &&
- std::is_enum_v<T>>> {
- static void Stringify(const T& v, std::ostringstream& ss) {
- using UT = typename std::underlying_type_t<T>;
- ToStringHelper<UT>::Stringify(static_cast<UT>(v), ss);
- }
-};
-
-template <typename... Ts>
-std::string ToString(const Ts&... values) {
- std::ostringstream ss;
- (ToStringHelper<typename std::remove_cvref_t<decltype(values)>>::Stringify(
- values, ss),
- ...);
- return ss.str();
-}
-
// ASCII-specific tolower. The standard library's tolower is locale sensitive,
// so we don't want to use it here.
template <typename CharT,
diff --git a/base/strings/string_util_unittest.cc b/base/strings/string_util_unittest.cc
index aba0e3143..4bb22e54c 100644
--- a/base/strings/string_util_unittest.cc
+++ b/base/strings/string_util_unittest.cc
@@ -1351,80 +1351,6 @@ TEST(StringUtilTest, MakeBasicStringPieceTest) {
EXPECT_TRUE(MakeWStringPiece(baz.end(), baz.end()).empty());
}
-enum class StreamableTestEnum { kGreeting, kLocation };
-enum class NonStreamableTestEnum { kGreeting = 0, kLocation };
-
-std::ostream& operator<<(std::ostream& os, const StreamableTestEnum& value) {
- switch (value) {
- case StreamableTestEnum::kGreeting:
- return os << "hello";
- case StreamableTestEnum::kLocation:
- return os << "world";
- }
-}
-
-class HasToString {
- public:
- std::string ToString() const { return "yay!"; }
-};
-
-class UnusualToString {
- public:
- HasToString ToString() const { return HasToString(); }
-};
-
-void Func() {}
-
-class NotStringifiable {};
-
-class OverloadsAddressOp {
- public:
- OverloadsAddressOp* operator&() { return nullptr; }
- const OverloadsAddressOp* operator&() const { return nullptr; }
-};
-
-TEST(StringUtilTest, ToString) {
- // Types with built-in <<.
- EXPECT_EQ(ToString("foo"), "foo");
- EXPECT_EQ(ToString(123), "123");
-
- // Type with user-defined <<.
- EXPECT_EQ(ToString(StreamableTestEnum::kGreeting), "hello");
- EXPECT_EQ(ToString(StreamableTestEnum::kGreeting, " ",
- StreamableTestEnum::kLocation),
- "hello world");
-
- // Type with user-defined ToString().
- EXPECT_EQ(ToString(HasToString()), "yay!");
-
- // Types with a ToString() that does not directly return a std::string should
- // still work.
- EXPECT_EQ(ToString(UnusualToString()), "yay!");
-
- // Scoped enums without a defined << should print as their underlying type.
- EXPECT_EQ(ToString(NonStreamableTestEnum::kLocation), "1");
-
- // I/O manipulators should have their expected effect, not be printed as
- // function pointers.
- EXPECT_EQ(ToString("42 in hex is ", std::hex, 42), "42 in hex is 2a");
-
- // We don't care about the actual address, but a function pointer should not
- // be implicitly converted to bool.
- EXPECT_NE(ToString(&Func), ToString(true));
-
- // Functions should be treated like function pointers.
- EXPECT_EQ(ToString(Func), ToString(&Func));
-
- // Non-stringifiable types should be printed using a fallback.
- EXPECT_NE(ToString(NotStringifiable()).find("-byte object at 0x"),
- std::string::npos);
-
- // Non-stringifiable types which overload operator& should print their real
- // address.
- EXPECT_NE(ToString(OverloadsAddressOp()),
- ToString(static_cast<OverloadsAddressOp*>(nullptr)));
-}
-
TEST(StringUtilTest, RemoveChars) {
const char kRemoveChars[] = "-/+*";
std::string input = "A-+bc/d!*";
diff --git a/base/strings/to_string.h b/base/strings/to_string.h
new file mode 100644
index 000000000..216963efe
--- /dev/null
+++ b/base/strings/to_string.h
@@ -0,0 +1,110 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_TO_STRING_H_
+#define BASE_STRINGS_TO_STRING_H_
+
+#include <ios>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <type_traits>
+
+#include "base/template_util.h"
+#include "base/types/supports_ostream_operator.h"
+
+namespace base {
+
+namespace internal {
+
+// I/O manipulators are function pointers, but should be sent directly to the
+// `ostream` instead of being cast to `const void*` like other function
+// pointers.
+template <typename T, typename = void>
+constexpr bool IsIomanip = false;
+template <typename T>
+constexpr bool
+ IsIomanip<T&(T&), std::enable_if_t<std::is_base_of_v<std::ios_base, T>>> =
+ true;
+
+// Function pointers implicitly convert to `bool`, so use this to avoid printing
+// function pointers as 1 or 0.
+template <typename T, typename = void>
+constexpr bool WillBeIncorrectlyStreamedAsBool = false;
+template <typename T>
+constexpr bool WillBeIncorrectlyStreamedAsBool<
+ T,
+ std::enable_if_t<std::is_function_v<std::remove_pointer_t<T>> &&
+ !IsIomanip<std::remove_pointer_t<T>>>> = true;
+
+// Fallback case when there is no better representation.
+template <typename T, typename = void>
+struct ToStringHelper {
+ static void Stringify(const T& v, std::ostringstream& ss) {
+ ss << "[" << sizeof(v) << "-byte object at 0x" << std::addressof(v) << "]";
+ }
+};
+
+// Most streamables.
+template <typename T>
+struct ToStringHelper<
+ T,
+ std::enable_if_t<SupportsOstreamOperator<const T&>::value &&
+ !WillBeIncorrectlyStreamedAsBool<T>>> {
+ static void Stringify(const T& v, std::ostringstream& ss) { ss << v; }
+};
+
+// Functions and function pointers.
+template <typename T>
+struct ToStringHelper<
+ T,
+ std::enable_if_t<SupportsOstreamOperator<const T&>::value &&
+ WillBeIncorrectlyStreamedAsBool<T>>> {
+ static void Stringify(const T& v, std::ostringstream& ss) {
+ ToStringHelper<const void*>::Stringify(reinterpret_cast<const void*>(v),
+ ss);
+ }
+};
+
+// Non-streamables that have a `ToString` member.
+template <typename T>
+struct ToStringHelper<
+ T,
+ std::enable_if_t<!SupportsOstreamOperator<const T&>::value &&
+ SupportsToString<const T&>::value>> {
+ static void Stringify(const T& v, std::ostringstream& ss) {
+ // .ToString() may not return a std::string, e.g. blink::WTF::String.
+ ToStringHelper<decltype(v.ToString())>::Stringify(v.ToString(), ss);
+ }
+};
+
+// Non-streamable enums (i.e. scoped enums where no `operator<<` overload was
+// declared).
+template <typename T>
+struct ToStringHelper<
+ T,
+ std::enable_if_t<!SupportsOstreamOperator<const T&>::value &&
+ std::is_enum_v<T>>> {
+ static void Stringify(const T& v, std::ostringstream& ss) {
+ using UT = typename std::underlying_type_t<T>;
+ ToStringHelper<UT>::Stringify(static_cast<UT>(v), ss);
+ }
+};
+
+} // namespace internal
+
+// Converts any type to a string, preferring defined operator<<() or ToString()
+// methods if they exist.
+template <typename... Ts>
+std::string ToString(const Ts&... values) {
+ std::ostringstream ss;
+ (internal::ToStringHelper<remove_cvref_t<decltype(values)>>::Stringify(values,
+ ss),
+ ...);
+ return ss.str();
+}
+
+} // namespace base
+
+#endif // BASE_STRINGS_TO_STRING_H_
diff --git a/base/strings/to_string_test.cc b/base/strings/to_string_test.cc
new file mode 100644
index 000000000..793820521
--- /dev/null
+++ b/base/strings/to_string_test.cc
@@ -0,0 +1,106 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/to_string.h"
+
+#include <ios>
+#include <ostream>
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+TEST(ToStringTest, Streamable) {
+ // Types with built-in <<.
+ EXPECT_EQ(ToString("foo"), "foo");
+ EXPECT_EQ(ToString(123), "123");
+}
+
+enum class StreamableTestEnum { kGreeting, kLocation };
+
+std::ostream& operator<<(std::ostream& os, const StreamableTestEnum& value) {
+ switch (value) {
+ case StreamableTestEnum::kGreeting:
+ return os << "hello";
+ case StreamableTestEnum::kLocation:
+ return os << "world";
+ }
+}
+
+TEST(ToStringTest, UserDefinedStreamable) {
+ // Type with user-defined <<.
+ EXPECT_EQ(ToString(StreamableTestEnum::kGreeting), "hello");
+ EXPECT_EQ(ToString(StreamableTestEnum::kGreeting, " ",
+ StreamableTestEnum::kLocation),
+ "hello world");
+}
+
+class HasToString {
+ public:
+ std::string ToString() const { return "yay!"; }
+};
+
+TEST(ToStringTest, UserDefinedToString) {
+ // Type with user-defined ToString().
+ EXPECT_EQ(ToString(HasToString()), "yay!");
+}
+
+class UnusualToString {
+ public:
+ HasToString ToString() const { return HasToString(); }
+};
+
+TEST(ToStringTest, ToStringReturnsNonStdString) {
+ // Types with a ToString() that does not directly return a std::string should
+ // still work.
+ EXPECT_EQ(ToString(UnusualToString()), "yay!");
+}
+
+enum class NonStreamableTestEnum { kGreeting = 0, kLocation };
+
+TEST(ToStringTest, ScopedEnum) {
+ // Scoped enums without a defined << should print as their underlying type.
+ EXPECT_EQ(ToString(NonStreamableTestEnum::kLocation), "1");
+}
+
+TEST(ToStringTest, IoManip) {
+ // I/O manipulators should have their expected effect, not be printed as
+ // function pointers.
+ EXPECT_EQ(ToString("42 in hex is ", std::hex, 42), "42 in hex is 2a");
+}
+
+void Func() {}
+
+TEST(ToStringTest, FunctionPointer) {
+ // We don't care about the actual address, but a function pointer should not
+ // be implicitly converted to bool.
+ EXPECT_NE(ToString(&Func), ToString(true));
+
+ // Functions should be treated like function pointers.
+ EXPECT_EQ(ToString(Func), ToString(&Func));
+}
+
+class NotStringifiable {};
+
+class OverloadsAddressOp {
+ public:
+ OverloadsAddressOp* operator&() { return nullptr; }
+ const OverloadsAddressOp* operator&() const { return nullptr; }
+};
+
+TEST(ToStringTest, NonStringifiable) {
+ // Non-stringifiable types should be printed using a fallback.
+ EXPECT_NE(ToString(NotStringifiable()).find("-byte object at 0x"),
+ std::string::npos);
+
+ // Non-stringifiable types which overload operator& should print their real
+ // address.
+ EXPECT_NE(ToString(OverloadsAddressOp()),
+ ToString(static_cast<OverloadsAddressOp*>(nullptr)));
+}
+
+} // namespace
+} // namespace base
diff --git a/base/system/sys_info.cc b/base/system/sys_info.cc
index 2c5bfc9bb..9d52ed326 100644
--- a/base/system/sys_info.cc
+++ b/base/system/sys_info.cc
@@ -8,6 +8,7 @@
#include "base/base_switches.h"
#include "base/command_line.h"
+#include "base/features.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
@@ -76,6 +77,47 @@ bool SysInfo::IsLowEndDevice() {
return IsLowEndDeviceImpl();
}
+#if BUILDFLAG(IS_ANDROID)
+
+namespace {
+
+bool IsAndroid4GbOr6GbDevice() {
+ // Because of Android carveouts, AmountOfPhysicalMemory() returns smaller
+ // than the actual memory size, So we will use a small lowerbound than 4GB
+ // to discriminate real 4GB devices from lower memory ones.
+ constexpr int kLowerBoundMB = 3.2 * 1024;
+ constexpr int kUpperBoundMB = 6 * 1024;
+ static bool is_4gb_or_6g_device =
+ kLowerBoundMB <= base::SysInfo::AmountOfPhysicalMemoryMB() &&
+ base::SysInfo::AmountOfPhysicalMemoryMB() <= kUpperBoundMB;
+ return is_4gb_or_6g_device;
+}
+
+bool IsPartialLowEndModeOnMidRangeDevicesEnabled() {
+ // TODO(crbug.com/1434873): make the feature not enable on 32-bit devices
+ // before launching or going to high Stable %.
+ return IsAndroid4GbOr6GbDevice() &&
+ base::FeatureList::IsEnabled(
+ features::kPartialLowEndModeOnMidRangeDevices);
+}
+
+} // namespace
+
+#endif // BUILDFLAG(IS_ANDROID)
+
+// TODO(crbug.com/1434873): This method is for chromium native code.
+// We need to update the java-side code, i.e.
+// base/android/java/src/org/chromium/base/SysUtils.java,
+// and to make the selected components in java to see this feature.
+bool SysInfo::IsLowEndDeviceOrPartialLowEndModeEnabled() {
+#if BUILDFLAG(IS_ANDROID)
+ return base::SysInfo::IsLowEndDevice() ||
+ IsPartialLowEndModeOnMidRangeDevicesEnabled();
+#else
+ return base::SysInfo::IsLowEndDevice();
+#endif
+}
+
#if !BUILDFLAG(IS_ANDROID)
// The Android equivalent of this lives in `detectLowEndDevice()` at:
// base/android/java/src/org/chromium/base/SysUtils.java
diff --git a/base/system/sys_info.h b/base/system/sys_info.h
index 94389b0ff..02f3006ee 100644
--- a/base/system/sys_info.h
+++ b/base/system/sys_info.h
@@ -238,6 +238,14 @@ class BASE_EXPORT SysInfo {
// On Desktop this returns true when memory <= 2GB.
static bool IsLowEndDevice();
+ // The same as IsLowEndDevice() except on Android.
+ //
+ // On Android this returns:
+ // true when IsLowEndDevice() returns true.
+ // true when the physical memory of the device is 4gb or 6gb and
+ // the feature: kPartialLowEndModeOnMidEndDevices() is enabled.
+ static bool IsLowEndDeviceOrPartialLowEndModeEnabled();
+
#if BUILDFLAG(IS_MAC)
// Sets whether CPU security mitigations are enabled for the current process.
// This is used to control the behavior of NumberOfProcessors(), see comment
diff --git a/base/task/cancelable_task_tracker.cc b/base/task/cancelable_task_tracker.cc
index 0acac4179..79e0d99e2 100644
--- a/base/task/cancelable_task_tracker.cc
+++ b/base/task/cancelable_task_tracker.cc
@@ -8,13 +8,11 @@
#include <utility>
-#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
-#include "base/task/scoped_set_task_priority_for_current_thread.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
@@ -30,52 +28,6 @@ void RunOrPostToTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner,
task_runner->PostTask(FROM_HERE, std::move(closure));
}
-// TODO(https://crbug.com/1009795): Remove these once we have established
-// whether off-sequence cancelation is worthwhile.
-
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class TaskStatus {
- kSameSequenceLive = 0,
- kOffSequenceLive = 1,
- kSameSequenceCanceled = 2,
- kOffSequenceCanceled = 3,
- kMaxValue = kOffSequenceCanceled,
-};
-
-void UmaRecordTaskDuration(bool same_sequence,
- bool background,
- bool canceled,
- TimeDelta duration) {
-#define DECLARE_HISTOGRAM(suffix) \
- Histogram::FactoryTimeGet( \
- "Scheduler.CancelableTaskTracker.TaskDuration2_" suffix, \
- Milliseconds(1), Seconds(10), 50, Histogram::kUmaTargetedHistogramFlag)
-
- static HistogramBase* histograms[] = {
- DECLARE_HISTOGRAM("LiveForegroundOffSequence"),
- DECLARE_HISTOGRAM("LiveForegroundSameSequence"),
- DECLARE_HISTOGRAM("LiveBackgroundOffSequence"),
- DECLARE_HISTOGRAM("LiveBackgroundSameSequence"),
- DECLARE_HISTOGRAM("CanceledForegroundOffSequence"),
- DECLARE_HISTOGRAM("CanceledForegroundSameSequence"),
- DECLARE_HISTOGRAM("CanceledBackgroundOffSequence"),
- DECLARE_HISTOGRAM("CanceledBackgroundSameSequence")};
-
- int i = (same_sequence ? 1 : 0) + (background ? 2 : 0) + (canceled ? 4 : 0);
- histograms[i]->AddTimeMillisecondsGranularity(duration);
-}
-
-BASE_FEATURE(kAllowOffSequenceTaskCancelation,
- "AllowOffSequenceTaskCancelation",
- base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool AllowOffSequenceTaskCancelation() {
- if (!base::FeatureList::GetInstance())
- return true;
- return base::FeatureList::IsEnabled(kAllowOffSequenceTaskCancelation);
-}
-
} // namespace
// static
@@ -122,11 +74,8 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply(
OnceClosure untrack_closure =
BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id);
bool success = task_runner->PostTaskAndReply(
- from_here,
- BindOnce(&RunIfNotCanceled, SequencedTaskRunner::GetCurrentDefault(),
- flag, std::move(task)),
- BindOnce(&RunThenUntrackIfNotCanceled,
- SequencedTaskRunner::GetCurrentDefault(), flag, std::move(reply),
+ from_here, BindOnce(&RunIfNotCanceled, flag, std::move(task)),
+ BindOnce(&RunThenUntrackIfNotCanceled, flag, std::move(reply),
std::move(untrack_closure)));
if (!success)
@@ -152,14 +101,11 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId(
BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id);
// Will always run |untrack_closure| on current sequence.
- ScopedClosureRunner untrack_runner(BindOnce(
- &RunOrPostToTaskRunner, SequencedTaskRunner::GetCurrentDefault(),
- BindOnce(&RunIfNotCanceled, SequencedTaskRunner::GetCurrentDefault(),
- flag, std::move(untrack_closure))));
+ ScopedClosureRunner untrack_runner(
+ BindOnce(&RunOrPostToTaskRunner, SequencedTaskRunner::GetCurrentDefault(),
+ BindOnce(&RunIfNotCanceled, flag, std::move(untrack_closure))));
- *is_canceled_cb =
- BindRepeating(&IsCanceled, SequencedTaskRunner::GetCurrentDefault(), flag,
- std::move(untrack_runner));
+ *is_canceled_cb = BindRepeating(&IsCanceled, flag, std::move(untrack_runner));
Track(id, std::move(flag));
return id;
@@ -201,60 +147,27 @@ bool CancelableTaskTracker::HasTrackedTasks() const {
// static
void CancelableTaskTracker::RunIfNotCanceled(
- const scoped_refptr<SequencedTaskRunner>& origin_task_runner,
const scoped_refptr<TaskCancellationFlag>& flag,
OnceClosure task) {
- // TODO(https://crbug.com/1009795): Record durations for executed tasks,
- // correlated with whether the task runs on a background or foreground
- // sequence, and whether it is the same sequence as the CancelableTaskTracker.
- // Also correlate with whether the task was run despite being canceled, to
- // allow an experiment to assess the value of off-sequence cancelation.
-
- // Record canceled & off-sequence status for all tasks.
- const bool was_canceled = flag->data.IsSet();
- const bool same_sequence = origin_task_runner->RunsTasksInCurrentSequence();
- const TaskStatus task_status =
- was_canceled ? (same_sequence ? TaskStatus::kSameSequenceCanceled
- : TaskStatus::kOffSequenceCanceled)
- : (same_sequence ? TaskStatus::kSameSequenceLive
- : TaskStatus::kOffSequenceLive);
- UMA_HISTOGRAM_ENUMERATION("Scheduler.CancelableTaskTracker.TaskStatus",
- task_status);
-
- // Skip tasks if they are canceled, taking into account the off-sequence
- // cancelation experiment.
- const bool skip_task =
- was_canceled && (AllowOffSequenceTaskCancelation() || same_sequence);
- if (skip_task)
- return;
-
- // Run the task and record its duration.
- const TimeTicks before_task_ticks = TimeTicks::Now();
- std::move(task).Run();
- const TimeDelta duration = TimeTicks::Now() - before_task_ticks;
- const bool is_background =
- internal::GetTaskPriorityForCurrentThread() < TaskPriority::USER_VISIBLE;
- UmaRecordTaskDuration(same_sequence, is_background, was_canceled, duration);
+ if (!flag->data.IsSet()) {
+ std::move(task).Run();
+ }
}
// static
void CancelableTaskTracker::RunThenUntrackIfNotCanceled(
- const scoped_refptr<SequencedTaskRunner>& origin_task_runner,
const scoped_refptr<TaskCancellationFlag>& flag,
OnceClosure task,
OnceClosure untrack) {
- RunIfNotCanceled(origin_task_runner, flag, std::move(task));
- RunIfNotCanceled(origin_task_runner, flag, std::move(untrack));
+ RunIfNotCanceled(flag, std::move(task));
+ RunIfNotCanceled(flag, std::move(untrack));
}
// static
bool CancelableTaskTracker::IsCanceled(
- const scoped_refptr<SequencedTaskRunner>& origin_task_runner,
const scoped_refptr<TaskCancellationFlag>& flag,
const ScopedClosureRunner& cleanup_runner) {
- return flag->data.IsSet() &&
- (AllowOffSequenceTaskCancelation() ||
- origin_task_runner->RunsTasksInCurrentSequence());
+ return flag->data.IsSet();
}
void CancelableTaskTracker::Track(TaskId id,
diff --git a/base/task/cancelable_task_tracker.h b/base/task/cancelable_task_tracker.h
index 250c27450..e6325d9a1 100644
--- a/base/task/cancelable_task_tracker.h
+++ b/base/task/cancelable_task_tracker.h
@@ -132,19 +132,14 @@ class BASE_EXPORT CancelableTaskTracker {
// See https://crbug.com/918948.
using TaskCancellationFlag = RefCountedData<AtomicFlag>;
- static void RunIfNotCanceled(
- const scoped_refptr<SequencedTaskRunner>& origin_task_runner,
- const scoped_refptr<TaskCancellationFlag>& flag,
- OnceClosure task);
+ static void RunIfNotCanceled(const scoped_refptr<TaskCancellationFlag>& flag,
+ OnceClosure task);
static void RunThenUntrackIfNotCanceled(
- const scoped_refptr<SequencedTaskRunner>& origin_task_runner,
const scoped_refptr<TaskCancellationFlag>& flag,
OnceClosure task,
OnceClosure untrack);
- static bool IsCanceled(
- const scoped_refptr<SequencedTaskRunner>& origin_task_runner,
- const scoped_refptr<TaskCancellationFlag>& flag,
- const ScopedClosureRunner& cleanup_runner);
+ static bool IsCanceled(const scoped_refptr<TaskCancellationFlag>& flag,
+ const ScopedClosureRunner& cleanup_runner);
void Track(TaskId id, scoped_refptr<TaskCancellationFlag> flag);
void Untrack(TaskId id);
diff --git a/base/task/common/task_annotator.cc b/base/task/common/task_annotator.cc
index 96416c886..367eda578 100644
--- a/base/task/common/task_annotator.cc
+++ b/base/task/common/task_annotator.cc
@@ -16,6 +16,7 @@
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/sys_byteorder.h"
+#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
#include "base/tracing_buildflags.h"
#include "build/build_config.h"
@@ -168,6 +169,13 @@ void TaskAnnotator::RunTaskImpl(PendingTask& pending_task) {
reinterpret_cast<void*>(pending_task.ipc_hash);
debug::Alias(&task_backtrace);
+ // Record the task time in convenient units. This can be compared to times
+ // stored in places like ReportThreadHang() and BrowserMain() when analyzing
+ // hangs.
+ const int64_t task_time =
+ pending_task.GetDesiredExecutionTime().since_origin().InSeconds();
+ base::debug::Alias(&task_time);
+
{
const AutoReset<PendingTask*> resetter(&current_pending_task,
&pending_task);
diff --git a/base/task/sequence_manager/thread_controller.h b/base/task/sequence_manager/thread_controller.h
index 692056dc5..138bdcdda 100644
--- a/base/task/sequence_manager/thread_controller.h
+++ b/base/task/sequence_manager/thread_controller.h
@@ -362,7 +362,7 @@ class BASE_EXPORT ThreadController {
absl::optional<perfetto::Track> perfetto_track_;
// True if tracing was enabled during the last pass of RecordTimeInPhase.
- bool was_tracing_enabled_;
+ bool was_tracing_enabled_ = false;
#endif
const raw_ref<const RunLevelTracker> outer_;
} time_keeper_{*this};
diff --git a/base/template_util.h b/base/template_util.h
index 415b3acf1..e192110d0 100644
--- a/base/template_util.h
+++ b/base/template_util.h
@@ -18,15 +18,6 @@ namespace base {
namespace internal {
-// Uses expression SFINAE to detect whether using operator<< would work.
-template <typename T, typename = void>
-struct SupportsOstreamOperator : std::false_type {};
-template <typename T>
-struct SupportsOstreamOperator<T,
- decltype(void(std::declval<std::ostream&>()
- << std::declval<T>()))>
- : std::true_type {};
-
template <typename T, typename = void>
struct SupportsToString : std::false_type {};
template <typename T>
diff --git a/base/template_util_unittest.cc b/base/template_util_unittest.cc
index 2774b5cac..efcea3d6d 100644
--- a/base/template_util_unittest.cc
+++ b/base/template_util_unittest.cc
@@ -4,6 +4,8 @@
#include "base/template_util.h"
+#include <stdint.h>
+
#include <string>
#include <type_traits>
@@ -17,58 +19,11 @@ namespace {
enum SimpleEnum { SIMPLE_ENUM };
enum EnumWithExplicitType : uint64_t { ENUM_WITH_EXPLICIT_TYPE };
enum class ScopedEnum { SCOPED_ENUM };
-enum class ScopedEnumWithOperator { SCOPED_ENUM_WITH_OPERATOR };
-std::ostream& operator<<(std::ostream& os, ScopedEnumWithOperator v) {
- return os;
-}
struct SimpleStruct {};
-struct StructWithOperator {};
-std::ostream& operator<<(std::ostream& os, const StructWithOperator& v) {
- return os;
-}
struct StructWithToString {
std::string ToString() const { return ""; }
};
-// A few standard types that definitely support printing.
-static_assert(internal::SupportsOstreamOperator<int>::value,
- "ints should be printable");
-static_assert(internal::SupportsOstreamOperator<const char*>::value,
- "C strings should be printable");
-static_assert(internal::SupportsOstreamOperator<std::string>::value,
- "std::string should be printable");
-
-// Various kinds of enums operator<< support.
-static_assert(internal::SupportsOstreamOperator<SimpleEnum>::value,
- "simple enum should be printable by value");
-static_assert(internal::SupportsOstreamOperator<const SimpleEnum&>::value,
- "simple enum should be printable by const ref");
-static_assert(internal::SupportsOstreamOperator<EnumWithExplicitType>::value,
- "enum with explicit type should be printable by value");
-static_assert(
- internal::SupportsOstreamOperator<const EnumWithExplicitType&>::value,
- "enum with explicit type should be printable by const ref");
-static_assert(!internal::SupportsOstreamOperator<ScopedEnum>::value,
- "scoped enum should not be printable by value");
-static_assert(!internal::SupportsOstreamOperator<const ScopedEnum&>::value,
- "simple enum should not be printable by const ref");
-static_assert(internal::SupportsOstreamOperator<ScopedEnumWithOperator>::value,
- "scoped enum with operator<< should be printable by value");
-static_assert(
- internal::SupportsOstreamOperator<const ScopedEnumWithOperator&>::value,
- "scoped enum with operator<< should be printable by const ref");
-
-// operator<< support on structs.
-static_assert(!internal::SupportsOstreamOperator<SimpleStruct>::value,
- "simple struct should not be printable by value");
-static_assert(!internal::SupportsOstreamOperator<const SimpleStruct&>::value,
- "simple struct should not be printable by const ref");
-static_assert(internal::SupportsOstreamOperator<StructWithOperator>::value,
- "struct with operator<< should be printable by value");
-static_assert(
- internal::SupportsOstreamOperator<const StructWithOperator&>::value,
- "struct with operator<< should be printable by const ref");
-
// .ToString() support on structs.
static_assert(!internal::SupportsToString<SimpleStruct>::value,
"simple struct value doesn't support .ToString()");
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index c4c6b2cd2..cc75b7235 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -13,6 +13,10 @@ if (is_android) {
import("//build/config/android/rules.gni")
}
+if (is_ios) {
+ import("//build/config/ios/rules.gni")
+}
+
static_library("test_config") {
testonly = true
sources = [
@@ -168,10 +172,16 @@ static_library("test_support") {
if (enable_base_tracing) {
public_deps += [ "//third_party/perfetto:perfetto_test_support" ]
- if (!is_chromeos && !is_ios) {
+ if (!is_chromeos) {
# TODO(rasikan): Add to ios and chromeos when unblocked by the chromiumos
# change to add the shared lib to the chrome-binary-tests directory.
public_deps += [ ":test_trace_processor" ]
+ if (is_ios) {
+ deps += [
+ ":test_trace_processor+bundle",
+ ":test_trace_processor+link",
+ ]
+ }
}
}
@@ -243,7 +253,10 @@ static_library("test_support") {
"test_support_ios.h",
"test_support_ios.mm",
]
- deps += [ ":google_test_runner_shared_headers" ]
+ deps += [
+ ":google_test_runner_shared_headers",
+ "//build:blink_buildflags",
+ ]
# With blink, we use the standard unit_test_launcher.cc.
if (!use_blink) {
@@ -477,7 +490,12 @@ if (enable_base_tracing) {
# processor depends on dev_sqlite. The two share the same symbols but have
# different implementations, so we need to hide dev_sqlite in this shared
# library even in non-component builds to prevent duplicate symbols.
- shared_library("test_trace_processor") {
+ _target_type = "shared_library"
+ if (is_ios) {
+ _target_type = "ios_framework_bundle"
+ }
+
+ target(_target_type, "test_trace_processor") {
defines = [ "TEST_TRACE_PROCESSOR_IMPL" ]
testonly = true
sources = [
@@ -493,6 +511,12 @@ if (enable_base_tracing) {
configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
}
+ if (is_ios) {
+ info_plist = "test_trace_processor.plist"
+ output_name = "TestTraceProcessor"
+ bundle_deps_filter = [ "//third_party/icu:icudata" ]
+ }
+
# Set rpath on dependent tests so that they can find the shared library
# in a non-component build.
if (!is_component_build) {
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
index 7f4d192ef..208b7b663 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
@@ -237,7 +237,7 @@ public class BaseJUnit4ClassRunner extends AndroidJUnit4ClassRunner {
@CallSuper
protected List<TestRule> getDefaultTestRules() {
return Arrays.asList(new BaseJUnit4TestRule(), new MockitoErrorHandler(),
- new UnitTestLifetimeAssertRule());
+ new UnitTestLifetimeAssertRule(), new ResettersForTestingTestRule());
}
/**
diff --git a/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java b/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java
new file mode 100644
index 000000000..779730603
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java
@@ -0,0 +1,32 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.ResettersForTesting;
+
+/**
+ * Ensures that all resetters are cleaned up after a test. The resetters are registered through
+ * {@link ResettersForTesting#register(Runnable)} and are typically used whenever we have code that
+ * has <code>public static void setFooForTesting(...)</code> constructs.
+ */
+class ResettersForTestingTestRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } finally {
+ ResettersForTesting.executeResetters();
+ }
+ }
+ };
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
index 93c600725..9fccb2c84 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
@@ -170,16 +170,30 @@ public final class CommandLineFlags {
}
private static void restoreFlags(Set<String> flagsToRemove, Map<String, String> flagsToAdd) {
- for (Entry<String, String> flag : flagsToAdd.entrySet()) {
- CommandLine.getInstance().appendSwitchWithValue(flag.getKey(), flag.getValue());
- }
for (String flag : flagsToRemove) {
CommandLine.getInstance().removeSwitch(flag);
}
+ for (Entry<String, String> flag : flagsToAdd.entrySet()) {
+ if (flag.getValue() == null) {
+ CommandLine.getInstance().appendSwitch(flag.getKey());
+ } else {
+ CommandLine.getInstance().appendSwitchWithValue(flag.getKey(), flag.getValue());
+ }
+ }
}
private static void applyFlags(Set<String> flagsToAdd, Set<String> flagsToRemove,
Set<String> flagsToRemoveForRestore, Map<String, String> flagsToAddForRestore) {
+ if (flagsToRemove != null) {
+ for (String flag : flagsToRemove) {
+ if (CommandLine.getInstance().hasSwitch(flag)) {
+ String existingValue = CommandLine.getInstance().getSwitchValue(flag);
+ CommandLine.getInstance().removeSwitch(flag);
+ flagsToAddForRestore.put(flag, existingValue);
+ }
+ }
+ }
+
Set<String> enableFeatures = new HashSet<String>(getFeatureValues(ENABLE_FEATURES));
Set<String> disableFeatures = new HashSet<String>(getFeatureValues(DISABLE_FEATURES));
for (String flag : flagsToAdd) {
@@ -210,25 +224,24 @@ public final class CommandLineFlags {
if (enableFeatures.size() > 0) {
String existingValue = CommandLine.getInstance().getSwitchValue(ENABLE_FEATURES);
- flagsToAddForRestore.put(ENABLE_FEATURES, existingValue);
+ if (existingValue != null) {
+ flagsToAddForRestore.put(ENABLE_FEATURES, existingValue);
+ CommandLine.getInstance().removeSwitch(ENABLE_FEATURES);
+ }
CommandLine.getInstance().appendSwitchWithValue(
ENABLE_FEATURES, TextUtils.join(",", enableFeatures));
flagsToRemoveForRestore.add(ENABLE_FEATURES);
}
if (disableFeatures.size() > 0) {
String existingValue = CommandLine.getInstance().getSwitchValue(DISABLE_FEATURES);
- flagsToAddForRestore.put(DISABLE_FEATURES, existingValue);
+ if (existingValue != null) {
+ flagsToAddForRestore.put(DISABLE_FEATURES, existingValue);
+ CommandLine.getInstance().removeSwitch(DISABLE_FEATURES);
+ }
CommandLine.getInstance().appendSwitchWithValue(
DISABLE_FEATURES, TextUtils.join(",", disableFeatures));
flagsToRemoveForRestore.add(DISABLE_FEATURES);
}
- if (flagsToRemove == null) return;
- for (String flag : flagsToRemove) {
- if (CommandLine.getInstance().hasSwitch(flag)) {
- CommandLine.getInstance().removeSwitch(flag);
- flagsToAddForRestore.put(flag, null);
- }
- }
}
private static void updateFlagsForClass(Class<?> clazz, Set<String> flags) {
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java
index e17119188..1039f736e 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java
@@ -5,21 +5,35 @@
package org.chromium.base.test.util;
import android.os.Debug;
-import android.os.SystemClock;
/**
* Encapsulates timeout logic, and disables timeouts when debugger is attached.
*/
public class TimeoutTimer {
- private final long mEndTimeMs;
+ private static final boolean IS_REAL_ANDROID =
+ System.getProperty("java.class.path").endsWith(".apk");
+ private static final long MS_TO_NANO = 1000000;
+ private final long mEndTimeNano;
private final long mTimeoutMs;
+ static {
+ if (!IS_REAL_ANDROID) {
+ try {
+ // BaseRobolectricTestRunner marks this class as "DoNotAcquire" so that
+ // System.nanoTime() will not return fake time.
+ Class.forName("android.os.Debug");
+ assert false : "Cannot use TimeoutTimer without using BaseRobolectricTestRunner";
+ } catch (Throwable e) {
+ }
+ }
+ }
+
/**
* @param timeoutMs Relative time for the timeout (unscaled).
*/
public TimeoutTimer(long timeoutMs) {
mTimeoutMs = ScalableTimeout.scaleTimeout(timeoutMs);
- mEndTimeMs = SystemClock.uptimeMillis() + mTimeoutMs;
+ mEndTimeNano = System.nanoTime() + mTimeoutMs * MS_TO_NANO;
}
/** Whether this timer has expired. */
@@ -27,14 +41,24 @@ public class TimeoutTimer {
return getRemainingMs() == 0;
}
+ private static boolean shouldPauseTimeouts() {
+ if (IS_REAL_ANDROID) {
+ return Debug.isDebuggerConnected();
+ }
+ // Our test runner sets this when --wait-for-java-debugger is passed.
+ // This will cause tests to never time out since the value is not updated when debugger
+ // detaches (oh well).
+ return "true".equals(System.getProperty("chromium.jdwp_active"));
+ }
+
/** Returns how much time is left in milliseconds. */
public long getRemainingMs() {
- if (Debug.isDebuggerConnected()) {
+ if (shouldPauseTimeouts()) {
// Never decreases, but still short enough that it's safe to wait() on and have a
// timeout happen once the debugger detaches.
return mTimeoutMs;
}
- long ret = mEndTimeMs - SystemClock.uptimeMillis();
- return ret < 0 ? 0 : ret;
+ long ret = mEndTimeNano - System.nanoTime();
+ return ret < 0 ? 0 : ret / MS_TO_NANO;
}
}
diff --git a/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
index 2d63c583b..6b4eef24f 100644
--- a/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
+++ b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
@@ -9,7 +9,9 @@ import androidx.test.core.app.ApplicationProvider;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.robolectric.DefaultTestLifecycle;
+import org.robolectric.RobolectricTestRunner;
import org.robolectric.TestLifecycle;
+import org.robolectric.internal.bytecode.InstrumentationConfiguration;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BundleUtils;
@@ -17,6 +19,7 @@ import org.chromium.base.ContextUtils;
import org.chromium.base.Flag;
import org.chromium.base.LifetimeAssert;
import org.chromium.base.PathUtils;
+import org.chromium.base.ResettersForTesting;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
@@ -24,16 +27,16 @@ import org.chromium.base.metrics.UmaRecorderHolder;
import org.chromium.base.task.PostTask;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisabledTest;
-import org.chromium.testing.local.LocalRobolectricTestRunner;
+import org.chromium.base.test.util.TimeoutTimer;
import java.lang.reflect.Method;
/**
* A Robolectric Test Runner that configures Chromium-specific settings and initializes base
* globals. If initializing base globals is not desired, then {@link
- * org.chromium.testing.local.LocalRobolectricTestRunner} could be used.
+ * org.robolectric.RobolectricTestRunner} could be used directly.
*/
-public class BaseRobolectricTestRunner extends LocalRobolectricTestRunner {
+public class BaseRobolectricTestRunner extends RobolectricTestRunner {
/**
* Enables a per-test setUp / tearDown hook.
*/
@@ -66,6 +69,7 @@ public class BaseRobolectricTestRunner extends LocalRobolectricTestRunner {
} finally {
CommandLineFlags.tearDownMethod();
CommandLineFlags.tearDownClass();
+ ResettersForTesting.executeResetters();
ApplicationStatus.destroyForJUnitTests();
ContextUtils.clearApplicationContextForTests();
PathUtils.resetForTesting();
@@ -94,4 +98,11 @@ public class BaseRobolectricTestRunner extends LocalRobolectricTestRunner {
Class<?> testSuiteClass = method.getDeclaringClass();
return testSuiteClass.getAnnotation(DisabledTest.class) != null;
}
+
+ @Override
+ protected InstrumentationConfiguration createClassLoaderConfig(final FrameworkMethod method) {
+ return new InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
+ .doNotAcquireClass(TimeoutTimer.class) // Requires access to non-fake SystemClock.
+ .build();
+ }
}
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java b/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java
index c8550ae14..d591ec8f2 100644
--- a/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java
+++ b/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java
@@ -33,22 +33,20 @@ public class CommandLineFlagsNoClassAnnotationCheckTest {
}
@Test
- @CommandLineFlags.Add("some-switch")
+ @CommandLineFlags.Add("some-switch=method_value")
@CommandLineFlags.Remove("some-switch")
public void testAddThenRemoveSwitch_method() throws Throwable {
- Assert.assertTrue(
- "CommandLine switches should be empty after adding and removing the same switch",
- CommandLine.getInstance().getSwitches().isEmpty());
+ Assert.assertEquals(
+ "some-switch should be removed from the class level and added back, not ignored",
+ "method_value", CommandLine.getInstance().getSwitchValue("some-switch"));
}
@Test
@CommandLineFlags.Remove("some-switch")
- @CommandLineFlags.Add("some-switch")
+ @CommandLineFlags.Add("some-switch=method_value")
public void testRemoveThenAddSwitch_method() throws Throwable {
- // ".Add" rules apply before ".Remove" rules when annotating the same method/class,
- // regardless of the order the annotations are written.
- Assert.assertTrue(
- "CommandLine switches should be empty after removing and adding the same switch",
- CommandLine.getInstance().getSwitches().isEmpty());
+ Assert.assertEquals(
+ "some-switch should be removed from the class level and added back, not ignored",
+ "method_value", CommandLine.getInstance().getSwitchValue("some-switch"));
}
}
diff --git a/base/test/test_support_ios.mm b/base/test/test_support_ios.mm
index df7e8a9eb..f298c4e31 100644
--- a/base/test/test_support_ios.mm
+++ b/base/test/test_support_ios.mm
@@ -15,6 +15,7 @@
#import "base/test/ios/google_test_runner_delegate.h"
#include "base/test/test_suite.h"
#include "base/test/test_switches.h"
+#include "build/blink_buildflags.h"
#include "testing/coverage_util_ios.h"
// Springboard will kill any iOS app that fails to check in after launch within
@@ -226,9 +227,13 @@ bool IsSceneStartupEnabled() {
int exitStatus = [self runGoogleTests];
+ // The blink code path uses a spawning test launcher and this wait isn't
+ // really necessary for that code path.
+#if !BUILDFLAG(USE_BLINK)
// If a test app is too fast, it will exit before Instruments has has a
// a chance to initialize and no test results will be seen.
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
+#endif
_window.reset();
// Use the hidden selector to try and cleanly take down the app (otherwise
diff --git a/base/test/test_trace_processor.plist b/base/test/test_trace_processor.plist
new file mode 100644
index 000000000..449c5cc40
--- /dev/null
+++ b/base/test/test_trace_processor.plist
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleIdentifier</key>
+ <string>${BUNDLE_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+</dict>
+</plist>
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index 8d2218ae9..b7e866466 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -1093,10 +1093,28 @@ message BlinkHighEntropyAPI {
// similar to "Navigator.languages.get".
optional string identifier = 1;
repeated JSFunctionArgument func_arguments = 2;
+
+ // Deprecated in favour of outer source_location.
optional BlinkSourceLocation source_location = 3;
}
optional BlinkExecutionContext execution_context = 1;
optional CalledJsApi called_api = 2;
+ optional BlinkSourceLocation source_location = 3;
+
+ // Describes lookup of a font.
+ message FontLookup {
+ enum FontLookupType {
+ FONT_LOOKUP_UNKNOWN_TYPE = 0;
+ FONT_LOOKUP_UNIQUE_OR_FAMILY_NAME = 1;
+ FONT_LOOKUP_UNIQUE_NAME_ONLY = 2;
+ }
+ optional FontLookupType type = 1;
+ optional string name = 2;
+ optional uint64 weight = 3;
+ optional uint64 width = 4;
+ optional uint64 slope = 5;
+ }
+ optional FontLookup font_lookup = 4;
}
// Contains information about a tab switch measurement.
diff --git a/base/types/strong_alias.h b/base/types/strong_alias.h
index 8a757be57..7a4872a4a 100644
--- a/base/types/strong_alias.h
+++ b/base/types/strong_alias.h
@@ -9,8 +9,8 @@
#include <type_traits>
#include <utility>
-#include "base/template_util.h"
#include "base/trace_event/base_tracing_forward.h"
+#include "base/types/supports_ostream_operator.h"
namespace base {
@@ -154,7 +154,7 @@ class StrongAlias {
template <typename TagType,
typename UnderlyingType,
typename = std::enable_if_t<
- base::internal::SupportsOstreamOperator<UnderlyingType>::value>>
+ internal::SupportsOstreamOperator<UnderlyingType>::value>>
std::ostream& operator<<(std::ostream& stream,
const StrongAlias<TagType, UnderlyingType>& alias) {
return stream << alias.value();
diff --git a/base/types/strong_alias_unittest.cc b/base/types/strong_alias_unittest.cc
index 6cc211adc..7ab1444d1 100644
--- a/base/types/strong_alias_unittest.cc
+++ b/base/types/strong_alias_unittest.cc
@@ -14,7 +14,7 @@
#include <utility>
#include "base/strings/string_piece.h"
-#include "base/template_util.h"
+#include "base/types/supports_ostream_operator.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(ENABLE_BASE_TRACING)
@@ -371,8 +371,7 @@ TEST(StrongAliasTest, EnsureConstexpr) {
void StreamOperatorExists() {
// Aliases of ints should be streamable because ints are streamable.
using StreamableAlias = StrongAlias<class IntTag, int>;
- static_assert(base::internal::SupportsOstreamOperator<StreamableAlias>::value,
- "");
+ static_assert(internal::SupportsOstreamOperator<StreamableAlias>::value);
// Aliases of a class which does not expose a stream operator should
// themselves not be streamable.
@@ -381,8 +380,7 @@ void StreamOperatorExists() {
Scope() = default;
};
using NonStreamableAlias = StrongAlias<class ScopeTag, Scope>;
- static_assert(
- !base::internal::SupportsOstreamOperator<NonStreamableAlias>::value, "");
+ static_assert(!internal::SupportsOstreamOperator<NonStreamableAlias>::value);
}
#if BUILDFLAG(ENABLE_BASE_TRACING)
diff --git a/base/types/supports_ostream_operator.h b/base/types/supports_ostream_operator.h
new file mode 100644
index 000000000..0803db486
--- /dev/null
+++ b/base/types/supports_ostream_operator.h
@@ -0,0 +1,28 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_
+#define BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_
+
+#include <ostream>
+#include <type_traits>
+#include <utility>
+
+namespace base::internal {
+
+// Uses expression SFINAE to detect whether using operator<< would work.
+//
+// Note that the above #include of <ostream> is necessary to guarantee
+// consistent results here for basic types.
+template <typename T, typename = void>
+struct SupportsOstreamOperator : std::false_type {};
+template <typename T>
+struct SupportsOstreamOperator<T,
+ decltype(void(std::declval<std::ostream&>()
+ << std::declval<T>()))>
+ : std::true_type {};
+
+} // namespace base::internal
+
+#endif // BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_
diff --git a/base/types/supports_ostream_operator_test.cc b/base/types/supports_ostream_operator_test.cc
new file mode 100644
index 000000000..0f8c3a09d
--- /dev/null
+++ b/base/types/supports_ostream_operator_test.cc
@@ -0,0 +1,68 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/types/supports_ostream_operator.h"
+
+#include <stdint.h>
+
+#include <ostream>
+#include <string>
+
+namespace base {
+namespace {
+
+enum SimpleEnum { SIMPLE_ENUM };
+enum EnumWithExplicitType : uint64_t { ENUM_WITH_EXPLICIT_TYPE };
+enum class ScopedEnum { SCOPED_ENUM };
+enum class ScopedEnumWithOperator { SCOPED_ENUM_WITH_OPERATOR };
+std::ostream& operator<<(std::ostream& os, ScopedEnumWithOperator v) {
+ return os;
+}
+struct SimpleStruct {};
+struct StructWithOperator {};
+std::ostream& operator<<(std::ostream& os, const StructWithOperator& v) {
+ return os;
+}
+
+// A few standard types that definitely support printing.
+static_assert(internal::SupportsOstreamOperator<int>::value,
+ "ints should be printable");
+static_assert(internal::SupportsOstreamOperator<const char*>::value,
+ "C strings should be printable");
+static_assert(internal::SupportsOstreamOperator<std::string>::value,
+ "std::string should be printable");
+
+// Various kinds of enums operator<< support.
+static_assert(internal::SupportsOstreamOperator<SimpleEnum>::value,
+ "simple enum should be printable by value");
+static_assert(internal::SupportsOstreamOperator<const SimpleEnum&>::value,
+ "simple enum should be printable by const ref");
+static_assert(internal::SupportsOstreamOperator<EnumWithExplicitType>::value,
+ "enum with explicit type should be printable by value");
+static_assert(
+ internal::SupportsOstreamOperator<const EnumWithExplicitType&>::value,
+ "enum with explicit type should be printable by const ref");
+static_assert(!internal::SupportsOstreamOperator<ScopedEnum>::value,
+ "scoped enum should not be printable by value");
+static_assert(!internal::SupportsOstreamOperator<const ScopedEnum&>::value,
+ "simple enum should not be printable by const ref");
+static_assert(internal::SupportsOstreamOperator<ScopedEnumWithOperator>::value,
+ "scoped enum with operator<< should be printable by value");
+static_assert(
+ internal::SupportsOstreamOperator<const ScopedEnumWithOperator&>::value,
+ "scoped enum with operator<< should be printable by const ref");
+
+// operator<< support on structs.
+static_assert(!internal::SupportsOstreamOperator<SimpleStruct>::value,
+ "simple struct should not be printable by value");
+static_assert(!internal::SupportsOstreamOperator<const SimpleStruct&>::value,
+ "simple struct should not be printable by const ref");
+static_assert(internal::SupportsOstreamOperator<StructWithOperator>::value,
+ "struct with operator<< should be printable by value");
+static_assert(
+ internal::SupportsOstreamOperator<const StructWithOperator&>::value,
+ "struct with operator<< should be printable by const ref");
+
+} // namespace
+} // namespace base
diff --git a/base/values.cc b/base/values.cc
index 2e51b987d..ac4cc65f3 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -1169,18 +1169,6 @@ std::string* Value::FindStringKey(StringPiece key) {
return GetDict().FindString(key);
}
-const Value* Value::FindDictKey(StringPiece key) const {
- const Value* result = GetDict().Find(key);
- if (!result || result->type() != Type::DICT) {
- return nullptr;
- }
- return result;
-}
-
-Value* Value::FindDictKey(StringPiece key) {
- return const_cast<Value*>(std::as_const(*this).FindDictKey(key));
-}
-
const Value* Value::FindListKey(StringPiece key) const {
const Value* result = GetDict().Find(key);
if (!result || result->type() != Type::LIST) {
diff --git a/base/values.h b/base/values.h
index 45dfea06a..d9e9e2df6 100644
--- a/base/values.h
+++ b/base/values.h
@@ -769,9 +769,6 @@ class BASE_EXPORT GSL_OWNER Value {
// DEPRECATED: prefer `Value::Dict::FindString()`.
const std::string* FindStringKey(StringPiece key) const;
std::string* FindStringKey(StringPiece key);
- // DEPRECATED: prefer `Value::Dict::FindDict()`.
- const Value* FindDictKey(StringPiece key) const;
- Value* FindDictKey(StringPiece key);
// DEPRECATED: prefer `Value::Dict::FindList()`.
const Value* FindListKey(StringPiece key) const;
Value* FindListKey(StringPiece key);
diff --git a/base/win/com_init_util.cc b/base/win/com_init_util.cc
index 0816fee42..b7b973c95 100644
--- a/base/win/com_init_util.cc
+++ b/base/win/com_init_util.cc
@@ -8,6 +8,7 @@
#include <winternl.h>
#include "base/logging.h"
+#include "base/memory/raw_ptr_exclusion.h"
#include "base/notreached.h"
namespace base {
@@ -27,8 +28,12 @@ struct OleTlsData {
MTA = 0x140,
};
- void* thread_base;
- void* sm_allocator;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #reinterpret-cast-trivial-type
+ RAW_PTR_EXCLUSION void* thread_base;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #reinterpret-cast-trivial-type
+ RAW_PTR_EXCLUSION void* sm_allocator;
DWORD apartment_id;
DWORD apartment_flags;
// There are many more fields than this, but for our purposes, we only care
diff --git a/base/win/iat_patch_function.cc b/base/win/iat_patch_function.cc
index 914cdd5b5..f9de69804 100644
--- a/base/win/iat_patch_function.cc
+++ b/base/win/iat_patch_function.cc
@@ -5,6 +5,7 @@
#include "base/win/iat_patch_function.h"
#include "base/check_op.h"
+#include "base/memory/raw_ptr_exclusion.h"
#include "base/notreached.h"
#include "base/win/patch_util.h"
#include "base/win/pe_image.h"
@@ -18,9 +19,15 @@ struct InterceptFunctionInformation {
bool finished_operation;
const char* imported_from_module;
const char* function_name;
- void* new_function;
- void** old_function;
- IMAGE_THUNK_DATA** iat_thunk;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #reinterpret-cast-trivial-type
+ RAW_PTR_EXCLUSION void* new_function;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #reinterpret-cast-trivial-type
+ RAW_PTR_EXCLUSION void** old_function;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #reinterpret-cast-trivial-type
+ RAW_PTR_EXCLUSION IMAGE_THUNK_DATA** iat_thunk;
DWORD return_code;
};
@@ -36,7 +43,9 @@ void* GetIATFunction(IMAGE_THUNK_DATA* iat_thunk) {
// or IMAGE_THUNK_DATA64 for correct pointer size.
union FunctionThunk {
IMAGE_THUNK_DATA thunk;
- void* pointer;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter
+ // for: #union
+ RAW_PTR_EXCLUSION void* pointer;
} iat_function;
iat_function.thunk = *iat_thunk;
diff --git a/base/win/iat_patch_function.h b/base/win/iat_patch_function.h
index 7b49baea7..3f3502d55 100644
--- a/base/win/iat_patch_function.h
+++ b/base/win/iat_patch_function.h
@@ -9,6 +9,7 @@
#include "base/base_export.h"
#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ptr_exclusion.h"
namespace base {
namespace win {
@@ -70,8 +71,12 @@ class BASE_EXPORT IATPatchFunction {
private:
HMODULE module_handle_ = nullptr;
raw_ptr<void> intercept_function_ = nullptr;
- void* original_function_ = nullptr;
- IMAGE_THUNK_DATA* iat_thunk_ = nullptr;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #addr-of
+ RAW_PTR_EXCLUSION void* original_function_ = nullptr;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #addr-of
+ RAW_PTR_EXCLUSION IMAGE_THUNK_DATA* iat_thunk_ = nullptr;
};
} // namespace win
diff --git a/base/win/registry.cc b/base/win/registry.cc
index 1ea20faeb..473f5fdc8 100644
--- a/base/win/registry.cc
+++ b/base/win/registry.cc
@@ -14,12 +14,16 @@
#include <vector>
#include "base/check_op.h"
+#include "base/containers/fixed_flat_map.h"
#include "base/functional/callback.h"
+#include "base/native_library.h"
#include "base/notreached.h"
+#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/string_util_win.h"
#include "base/threading/thread_restrictions.h"
#include "base/win/object_watcher.h"
+#include "base/win/pe_image.h"
#include "base/win/scoped_handle.h"
#include "base/win/shlwapi.h"
@@ -46,8 +50,326 @@ constexpr DWORD kInvalidIterValue = static_cast<DWORD>(-1);
} // namespace
+namespace internal {
+
+// A forwarder to the normal delayloaded Windows Registry API.
+class Standard {
+ public:
+ static inline LSTATUS CreateKey(HKEY hKey,
+ LPCWSTR lpSubKey,
+ DWORD Reserved,
+ LPWSTR lpClass,
+ DWORD dwOptions,
+ REGSAM samDesired,
+ CONST LPSECURITY_ATTRIBUTES
+ lpSecurityAttributes,
+ PHKEY phkResult,
+ LPDWORD lpdwDisposition) {
+ return ::RegCreateKeyExW(hKey, lpSubKey, Reserved, lpClass, dwOptions,
+ samDesired, lpSecurityAttributes, phkResult,
+ lpdwDisposition);
+ }
+
+ static inline LSTATUS OpenKey(HKEY hKey,
+ LPCWSTR lpSubKey,
+ DWORD ulOptions,
+ REGSAM samDesired,
+ PHKEY phkResult) {
+ return ::RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, phkResult);
+ }
+
+ static inline LSTATUS DeleteKey(HKEY hKey,
+ LPCWSTR lpSubKey,
+ REGSAM samDesired,
+ DWORD Reserved) {
+ return ::RegDeleteKeyExW(hKey, lpSubKey, samDesired, Reserved);
+ }
+
+ static inline LSTATUS QueryInfoKey(HKEY hKey,
+ LPWSTR lpClass,
+ LPDWORD lpcchClass,
+ LPDWORD lpReserved,
+ LPDWORD lpcSubKeys,
+ LPDWORD lpcbMaxSubKeyLen,
+ LPDWORD lpcbMaxClassLen,
+ LPDWORD lpcValues,
+ LPDWORD lpcbMaxValueNameLen,
+ LPDWORD lpcbMaxValueLen,
+ LPDWORD lpcbSecurityDescriptor,
+ PFILETIME lpftLastWriteTime) {
+ return ::RegQueryInfoKeyW(hKey, lpClass, lpcchClass, lpReserved, lpcSubKeys,
+ lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues,
+ lpcbMaxValueNameLen, lpcbMaxValueLen,
+ lpcbSecurityDescriptor, lpftLastWriteTime);
+ }
+
+ static inline LSTATUS EnumKey(HKEY hKey,
+ DWORD dwIndex,
+ LPWSTR lpName,
+ LPDWORD lpcchName,
+ LPDWORD lpReserved,
+ LPWSTR lpClass,
+ LPDWORD lpcchClass,
+ PFILETIME lpftLastWriteTime) {
+ return ::RegEnumKeyExW(hKey, dwIndex, lpName, lpcchName, lpReserved,
+ lpClass, lpcchClass, lpftLastWriteTime);
+ }
+
+ static inline LSTATUS CloseKey(HKEY hKey) { return ::RegCloseKey(hKey); }
+
+ static inline LSTATUS QueryValue(HKEY hKey,
+ LPCWSTR lpValueName,
+ LPDWORD lpReserved,
+ LPDWORD lpType,
+ LPBYTE lpData,
+ LPDWORD lpcbData) {
+ return ::RegQueryValueExW(hKey, lpValueName, lpReserved, lpType, lpData,
+ lpcbData);
+ }
+
+ static inline LSTATUS SetValue(HKEY hKey,
+ LPCWSTR lpValueName,
+ DWORD Reserved,
+ DWORD dwType,
+ CONST BYTE* lpData,
+ DWORD cbData) {
+ return ::RegSetValueExW(hKey, lpValueName, Reserved, dwType, lpData,
+ cbData);
+ }
+
+ static inline LSTATUS DeleteValue(HKEY hKey, LPCWSTR lpValueName) {
+ return ::RegDeleteValueW(hKey, lpValueName);
+ }
+
+ static inline LSTATUS EnumValue(HKEY hKey,
+ DWORD dwIndex,
+ LPWSTR lpValueName,
+ LPDWORD lpcchValueName,
+ LPDWORD lpReserved,
+ LPDWORD lpType,
+ LPBYTE lpData,
+ LPDWORD lpcbData) {
+ return ::RegEnumValueW(hKey, dwIndex, lpValueName, lpcchValueName,
+ lpReserved, lpType, lpData, lpcbData);
+ }
+};
+
+// An implementation derived from the export table of advapi32.
+class ExportDerived {
+ public:
+ static LSTATUS CreateKey(HKEY hKey,
+ LPCWSTR lpSubKey,
+ DWORD Reserved,
+ LPWSTR lpClass,
+ DWORD dwOptions,
+ REGSAM samDesired,
+ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ PHKEY phkResult,
+ LPDWORD lpdwDisposition) {
+ if (!ResolveRegistryFunctions() || !reg_create_key_ex_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_create_key_ex_(hKey, lpSubKey, Reserved, lpClass, dwOptions,
+ samDesired, lpSecurityAttributes, phkResult,
+ lpdwDisposition);
+ }
+
+ static LSTATUS OpenKey(HKEY hKey,
+ LPCWSTR lpSubKey,
+ DWORD ulOptions,
+ REGSAM samDesired,
+ PHKEY phkResult) {
+ if (!ResolveRegistryFunctions() || !reg_open_key_ex_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_open_key_ex_(hKey, lpSubKey, ulOptions, samDesired, phkResult);
+ }
+
+ static LSTATUS DeleteKey(HKEY hKey,
+ LPCWSTR lpSubKey,
+ REGSAM samDesired,
+ DWORD Reserved) {
+ if (!ResolveRegistryFunctions() || !reg_delete_key_ex_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_delete_key_ex_(hKey, lpSubKey, samDesired, Reserved);
+ }
+
+ static LSTATUS QueryInfoKey(HKEY hKey,
+ LPWSTR lpClass,
+ LPDWORD lpcchClass,
+ LPDWORD lpReserved,
+ LPDWORD lpcSubKeys,
+ LPDWORD lpcbMaxSubKeyLen,
+ LPDWORD lpcbMaxClassLen,
+ LPDWORD lpcValues,
+ LPDWORD lpcbMaxValueNameLen,
+ LPDWORD lpcbMaxValueLen,
+ LPDWORD lpcbSecurityDescriptor,
+ PFILETIME lpftLastWriteTime) {
+ if (!ResolveRegistryFunctions() || !reg_query_info_key_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_query_info_key_(hKey, lpClass, lpcchClass, lpReserved,
+ lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen,
+ lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen,
+ lpcbSecurityDescriptor, lpftLastWriteTime);
+ }
+
+ static LSTATUS EnumKey(HKEY hKey,
+ DWORD dwIndex,
+ LPWSTR lpName,
+ LPDWORD lpcchName,
+ LPDWORD lpReserved,
+ LPWSTR lpClass,
+ LPDWORD lpcchClass,
+ PFILETIME lpftLastWriteTime) {
+ if (!ResolveRegistryFunctions() || !reg_enum_key_ex_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_enum_key_ex_(hKey, dwIndex, lpName, lpcchName, lpReserved,
+ lpClass, lpcchClass, lpftLastWriteTime);
+ }
+
+ static LSTATUS CloseKey(HKEY hKey) {
+ if (!ResolveRegistryFunctions() || !reg_close_key_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_close_key_(hKey);
+ }
+
+ static LSTATUS QueryValue(HKEY hKey,
+ LPCWSTR lpValueName,
+ LPDWORD lpReserved,
+ LPDWORD lpType,
+ LPBYTE lpData,
+ LPDWORD lpcbData) {
+ if (!ResolveRegistryFunctions() || !reg_query_value_ex_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_query_value_ex_(hKey, lpValueName, lpReserved, lpType, lpData,
+ lpcbData);
+ }
+
+ static LSTATUS SetValue(HKEY hKey,
+ LPCWSTR lpValueName,
+ DWORD Reserved,
+ DWORD dwType,
+ CONST BYTE* lpData,
+ DWORD cbData) {
+ if (!ResolveRegistryFunctions() || !reg_set_value_ex_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_set_value_ex_(hKey, lpValueName, Reserved, dwType, lpData,
+ cbData);
+ }
+
+ static LSTATUS DeleteValue(HKEY hKey, LPCWSTR lpValueName) {
+ if (!ResolveRegistryFunctions() || !reg_delete_value_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+ return reg_delete_value_(hKey, lpValueName);
+ }
+ static LSTATUS EnumValue(HKEY hKey,
+ DWORD dwIndex,
+ LPWSTR lpValueName,
+ LPDWORD lpcchValueName,
+ LPDWORD lpReserved,
+ LPDWORD lpType,
+ LPBYTE lpData,
+ LPDWORD lpcbData) {
+ if (!ResolveRegistryFunctions() || !reg_enum_value_) {
+ return ERROR_ERRORS_ENCOUNTERED;
+ }
+
+ return reg_enum_value_(hKey, dwIndex, lpValueName, lpcchValueName,
+ lpReserved, lpType, lpData, lpcbData);
+ }
+
+ private:
+ static bool ProcessOneExport(const base::win::PEImage& image,
+ DWORD ordinal,
+ DWORD hint,
+ LPCSTR name,
+ PVOID function_addr,
+ LPCSTR forward,
+ PVOID cookie) {
+ if (!name || !function_addr) {
+ return true;
+ }
+
+ static const auto kMap =
+ base::MakeFixedFlatMapSorted<base::StringPiece, void**>({
+ {"RegCloseKey", reinterpret_cast<void**>(&reg_close_key_)},
+ {"RegCreateKeyExW", reinterpret_cast<void**>(&reg_create_key_ex_)},
+ {"RegDeleteKeyExW", reinterpret_cast<void**>(&reg_delete_key_ex_)},
+ {"RegDeleteValueW", reinterpret_cast<void**>(&reg_delete_value_)},
+ {"RegEnumKeyExW", reinterpret_cast<void**>(&reg_enum_key_ex_)},
+ {"RegEnumValueW", reinterpret_cast<void**>(&reg_enum_value_)},
+ {"RegOpenKeyExW", reinterpret_cast<void**>(&reg_open_key_ex_)},
+ {"RegQueryInfoKeyW",
+ reinterpret_cast<void**>(&reg_query_info_key_)},
+ {"RegQueryValueExW",
+ reinterpret_cast<void**>(&reg_query_value_ex_)},
+ {"RegSetValueExW", reinterpret_cast<void**>(&reg_set_value_ex_)},
+ });
+
+ auto* entry = kMap.find(name);
+ if (entry == kMap.end()) {
+ return true;
+ }
+
+ static size_t num_init_functions = 0;
+ if (!std::exchange(*(entry->second), function_addr)) {
+ ++num_init_functions;
+ }
+
+ bool& fully_resolved = *static_cast<bool*>(cookie);
+ fully_resolved = num_init_functions == kMap.size();
+ return !fully_resolved;
+ }
+
+ static bool ResolveRegistryFunctions() {
+ static bool initialized = []() {
+ base::NativeLibraryLoadError error;
+ HMODULE advapi32 = base::PinSystemLibrary(L"advapi32.dll", &error);
+ if (!advapi32 || error.code) {
+ return false;
+ }
+ bool fully_resolved = false;
+ base::win::PEImage(advapi32).EnumExports(&ProcessOneExport,
+ &fully_resolved);
+ return fully_resolved;
+ }();
+ return initialized;
+ }
+
+ static decltype(::RegCreateKeyExW)* reg_create_key_ex_;
+ static decltype(::RegOpenKeyExW)* reg_open_key_ex_;
+ static decltype(::RegDeleteKeyExW)* reg_delete_key_ex_;
+ static decltype(::RegQueryInfoKeyW)* reg_query_info_key_;
+ static decltype(::RegEnumKeyExW)* reg_enum_key_ex_;
+ static decltype(::RegCloseKey)* reg_close_key_;
+ static decltype(::RegQueryValueExW)* reg_query_value_ex_;
+ static decltype(::RegSetValueExW)* reg_set_value_ex_;
+ static decltype(::RegDeleteValueW)* reg_delete_value_;
+ static decltype(::RegEnumValueW)* reg_enum_value_;
+};
+
+decltype(::RegCreateKeyEx)* ExportDerived::reg_create_key_ex_ = nullptr;
+decltype(::RegOpenKeyExW)* ExportDerived::reg_open_key_ex_ = nullptr;
+decltype(::RegDeleteKeyExW)* ExportDerived::reg_delete_key_ex_ = nullptr;
+decltype(::RegQueryInfoKeyW)* ExportDerived::reg_query_info_key_ = nullptr;
+decltype(::RegEnumKeyExW)* ExportDerived::reg_enum_key_ex_ = nullptr;
+decltype(::RegCloseKey)* ExportDerived::reg_close_key_ = nullptr;
+decltype(::RegQueryValueEx)* ExportDerived::reg_query_value_ex_ = nullptr;
+decltype(::RegSetValueExW)* ExportDerived::reg_set_value_ex_ = nullptr;
+decltype(::RegDeleteValueW)* ExportDerived::reg_delete_value_ = nullptr;
+decltype(::RegEnumValueW)* ExportDerived::reg_enum_value_ = nullptr;
+
// Watches for modifications to a key.
-class RegKey::Watcher : public ObjectWatcher::Delegate {
+template <typename Reg>
+class GenericRegKey<Reg>::Watcher : public ObjectWatcher::Delegate {
public:
Watcher() = default;
@@ -71,15 +393,19 @@ class RegKey::Watcher : public ObjectWatcher::Delegate {
ChangeCallback callback_;
};
-bool RegKey::Watcher::StartWatching(HKEY key, ChangeCallback callback) {
+template <typename Reg>
+bool GenericRegKey<Reg>::Watcher::StartWatching(HKEY key,
+ ChangeCallback callback) {
DCHECK(key);
DCHECK(callback_.is_null());
- if (!watch_event_.is_valid())
+ if (!watch_event_.is_valid()) {
watch_event_.Set(CreateEvent(nullptr, TRUE, FALSE, nullptr));
+ }
- if (!watch_event_.is_valid())
+ if (!watch_event_.is_valid()) {
return false;
+ }
DWORD filter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES |
REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY |
@@ -97,25 +423,33 @@ bool RegKey::Watcher::StartWatching(HKEY key, ChangeCallback callback) {
return object_watcher_.StartWatchingOnce(watch_event_.get(), this);
}
-// RegKey ----------------------------------------------------------------------
+// GenericRegKey<Reg>
+// ----------------------------------------------------------------------
-RegKey::RegKey() = default;
+template <typename Reg>
+GenericRegKey<Reg>::GenericRegKey() = default;
-RegKey::RegKey(HKEY key) : key_(key) {}
+template <typename Reg>
+GenericRegKey<Reg>::GenericRegKey(HKEY key) : key_(key) {}
-RegKey::RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access) {
+template <typename Reg>
+GenericRegKey<Reg>::GenericRegKey(HKEY rootkey,
+ const wchar_t* subkey,
+ REGSAM access) {
if (rootkey) {
- if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK))
+ if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) {
Create(rootkey, subkey, access);
- else
+ } else {
Open(rootkey, subkey, access);
+ }
} else {
DCHECK(!subkey);
wow64access_ = access & kWow64AccessMask;
}
}
-RegKey::RegKey(RegKey&& other) noexcept
+template <typename Reg>
+GenericRegKey<Reg>::GenericRegKey(GenericRegKey<Reg>&& other) noexcept
: key_(other.key_),
wow64access_(other.wow64access_),
key_watcher_(std::move(other.key_watcher_)) {
@@ -123,7 +457,8 @@ RegKey::RegKey(RegKey&& other) noexcept
other.wow64access_ = 0;
}
-RegKey& RegKey::operator=(RegKey&& other) {
+template <typename Reg>
+GenericRegKey<Reg>& GenericRegKey<Reg>::operator=(GenericRegKey<Reg>&& other) {
Close();
std::swap(key_, other.key_);
std::swap(wow64access_, other.wow64access_);
@@ -131,23 +466,28 @@ RegKey& RegKey::operator=(RegKey&& other) {
return *this;
}
-RegKey::~RegKey() {
+template <typename Reg>
+GenericRegKey<Reg>::~GenericRegKey() {
Close();
}
-LONG RegKey::Create(HKEY rootkey, const wchar_t* subkey, REGSAM access) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::Create(HKEY rootkey,
+ const wchar_t* subkey,
+ REGSAM access) {
DWORD disposition_value;
return CreateWithDisposition(rootkey, subkey, &disposition_value, access);
}
-LONG RegKey::CreateWithDisposition(HKEY rootkey,
- const wchar_t* subkey,
- DWORD* disposition,
- REGSAM access) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::CreateWithDisposition(HKEY rootkey,
+ const wchar_t* subkey,
+ DWORD* disposition,
+ REGSAM access) {
DCHECK(rootkey && subkey && access && disposition);
HKEY subhkey = nullptr;
LONG result =
- RegCreateKeyEx(rootkey, subkey, 0, nullptr, REG_OPTION_NON_VOLATILE,
+ Reg::CreateKey(rootkey, subkey, 0, nullptr, REG_OPTION_NON_VOLATILE,
access, nullptr, &subhkey, disposition);
if (result == ERROR_SUCCESS) {
Close();
@@ -158,19 +498,21 @@ LONG RegKey::CreateWithDisposition(HKEY rootkey,
return result;
}
-LONG RegKey::CreateKey(const wchar_t* name, REGSAM access) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::CreateKey(const wchar_t* name, REGSAM access) {
DCHECK(name && access);
- // After the application has accessed an alternate registry view using one of
- // the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent operations
- // (create, delete, or open) on child registry keys must explicitly use the
- // same flag. Otherwise, there can be unexpected behavior.
+ // After the application has accessed an alternate registry view using one
+ // of the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent
+ // operations (create, delete, or open) on child registry keys must
+ // explicitly use the same flag. Otherwise, there can be unexpected
+ // behavior.
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx.
if ((access & kWow64AccessMask) != wow64access_) {
NOTREACHED();
return ERROR_INVALID_PARAMETER;
}
HKEY subkey = nullptr;
- LONG result = RegCreateKeyEx(key_, name, 0, nullptr, REG_OPTION_NON_VOLATILE,
+ LONG result = Reg::CreateKey(key_, name, 0, nullptr, REG_OPTION_NON_VOLATILE,
access, nullptr, &subkey, nullptr);
if (result == ERROR_SUCCESS) {
Close();
@@ -181,11 +523,14 @@ LONG RegKey::CreateKey(const wchar_t* name, REGSAM access) {
return result;
}
-LONG RegKey::Open(HKEY rootkey, const wchar_t* subkey, REGSAM access) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::Open(HKEY rootkey,
+ const wchar_t* subkey,
+ REGSAM access) {
DCHECK(rootkey && subkey && access);
HKEY subhkey = nullptr;
- LONG result = RegOpenKeyEx(rootkey, subkey, 0, access, &subhkey);
+ LONG result = Reg::OpenKey(rootkey, subkey, 0, access, &subhkey);
if (result == ERROR_SUCCESS) {
Close();
key_ = subhkey;
@@ -195,19 +540,22 @@ LONG RegKey::Open(HKEY rootkey, const wchar_t* subkey, REGSAM access) {
return result;
}
-LONG RegKey::OpenKey(const wchar_t* relative_key_name, REGSAM access) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::OpenKey(const wchar_t* relative_key_name,
+ REGSAM access) {
DCHECK(relative_key_name && access);
- // After the application has accessed an alternate registry view using one of
- // the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent operations
- // (create, delete, or open) on child registry keys must explicitly use the
- // same flag. Otherwise, there can be unexpected behavior.
+ // After the application has accessed an alternate registry view using one
+ // of the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent
+ // operations (create, delete, or open) on child registry keys must
+ // explicitly use the same flag. Otherwise, there can be unexpected
+ // behavior.
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx.
if ((access & kWow64AccessMask) != wow64access_) {
NOTREACHED();
return ERROR_INVALID_PARAMETER;
}
HKEY subkey = nullptr;
- LONG result = RegOpenKeyEx(key_, relative_key_name, 0, access, &subkey);
+ LONG result = Reg::OpenKey(key_, relative_key_name, 0, access, &subkey);
// We have to close the current opened key before replacing it with the new
// one.
@@ -219,62 +567,72 @@ LONG RegKey::OpenKey(const wchar_t* relative_key_name, REGSAM access) {
return result;
}
-void RegKey::Close() {
+template <typename Reg>
+void GenericRegKey<Reg>::Close() {
if (key_) {
- ::RegCloseKey(key_);
+ Reg::CloseKey(key_);
key_ = nullptr;
wow64access_ = 0;
}
}
-// TODO(wfh): Remove this and other unsafe methods. See http://crbug.com/375400
-void RegKey::Set(HKEY key) {
+// TODO(wfh): Remove this and other unsafe methods. See
+// http://crbug.com/375400
+template <typename Reg>
+void GenericRegKey<Reg>::Set(HKEY key) {
if (key_ != key) {
Close();
key_ = key;
}
}
-HKEY RegKey::Take() {
+template <typename Reg>
+HKEY GenericRegKey<Reg>::Take() {
DCHECK_EQ(wow64access_, 0u);
HKEY key = key_;
key_ = nullptr;
return key;
}
-bool RegKey::HasValue(const wchar_t* name) const {
- return RegQueryValueEx(key_, name, nullptr, nullptr, nullptr, nullptr) ==
+template <typename Reg>
+bool GenericRegKey<Reg>::HasValue(const wchar_t* name) const {
+ return Reg::QueryValue(key_, name, nullptr, nullptr, nullptr, nullptr) ==
ERROR_SUCCESS;
}
-DWORD RegKey::GetValueCount() const {
+template <typename Reg>
+DWORD GenericRegKey<Reg>::GetValueCount() const {
DWORD count = 0;
LONG result =
- RegQueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, &count, nullptr, nullptr, nullptr, nullptr);
+ Reg::QueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, &count, nullptr, nullptr, nullptr, nullptr);
return (result == ERROR_SUCCESS) ? count : 0;
}
-FILETIME RegKey::GetLastWriteTime() const {
+template <typename Reg>
+FILETIME GenericRegKey<Reg>::GetLastWriteTime() const {
FILETIME last_write_time;
- LONG result = RegQueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, &last_write_time);
+ LONG result = Reg::QueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, &last_write_time);
return (result == ERROR_SUCCESS) ? last_write_time : FILETIME{};
}
-LONG RegKey::GetValueNameAt(DWORD index, std::wstring* name) const {
+template <typename Reg>
+LONG GenericRegKey<Reg>::GetValueNameAt(DWORD index, std::wstring* name) const {
wchar_t buf[256];
DWORD bufsize = std::size(buf);
- LONG r = ::RegEnumValue(key_, index, buf, &bufsize, nullptr, nullptr, nullptr,
+ LONG r = Reg::EnumValue(key_, index, buf, &bufsize, nullptr, nullptr, nullptr,
nullptr);
- if (r == ERROR_SUCCESS)
+ if (r == ERROR_SUCCESS) {
name->assign(buf, bufsize);
+ }
return r;
}
-LONG RegKey::DeleteKey(const wchar_t* name) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::DeleteKey(const wchar_t* name) {
DCHECK(name);
// Verify the key exists before attempting delete to replicate previous
@@ -282,64 +640,75 @@ LONG RegKey::DeleteKey(const wchar_t* name) {
// `RegOpenKeyEx()` will return an error if `key_` is invalid.
HKEY subkey = nullptr;
LONG result =
- RegOpenKeyEx(key_, name, 0, READ_CONTROL | wow64access_, &subkey);
- if (result != ERROR_SUCCESS)
+ Reg::OpenKey(key_, name, 0, READ_CONTROL | wow64access_, &subkey);
+ if (result != ERROR_SUCCESS) {
return result;
- RegCloseKey(subkey);
+ }
+ Reg::CloseKey(subkey);
return RegDelRecurse(key_, name, wow64access_);
}
-LONG RegKey::DeleteEmptyKey(const wchar_t* name) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::DeleteEmptyKey(const wchar_t* name) {
DCHECK(name);
// `RegOpenKeyEx()` will return an error if `key_` is invalid.
HKEY target_key = nullptr;
LONG result =
- RegOpenKeyEx(key_, name, 0, KEY_READ | wow64access_, &target_key);
+ Reg::OpenKey(key_, name, 0, KEY_READ | wow64access_, &target_key);
- if (result != ERROR_SUCCESS)
+ if (result != ERROR_SUCCESS) {
return result;
+ }
DWORD count = 0;
result =
- RegQueryInfoKey(target_key, nullptr, nullptr, nullptr, nullptr, nullptr,
- nullptr, &count, nullptr, nullptr, nullptr, nullptr);
+ Reg::QueryInfoKey(target_key, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, &count, nullptr, nullptr, nullptr, nullptr);
- RegCloseKey(target_key);
+ Reg::CloseKey(target_key);
- if (result != ERROR_SUCCESS)
+ if (result != ERROR_SUCCESS) {
return result;
+ }
- if (count == 0)
+ if (count == 0) {
return RegDeleteKeyEx(key_, name, wow64access_, 0);
+ }
return ERROR_DIR_NOT_EMPTY;
}
-LONG RegKey::DeleteValue(const wchar_t* value_name) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::DeleteValue(const wchar_t* value_name) {
// `RegDeleteValue()` will return an error if `key_` is invalid.
- LONG result = RegDeleteValue(key_, value_name);
+ LONG result = Reg::DeleteValue(key_, value_name);
return result;
}
-LONG RegKey::ReadValueDW(const wchar_t* name, DWORD* out_value) const {
+template <typename Reg>
+LONG GenericRegKey<Reg>::ReadValueDW(const wchar_t* name,
+ DWORD* out_value) const {
DCHECK(out_value);
DWORD type = REG_DWORD;
DWORD size = sizeof(DWORD);
DWORD local_value = 0;
LONG result = ReadValue(name, &local_value, &size, &type);
if (result == ERROR_SUCCESS) {
- if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD))
+ if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD)) {
*out_value = local_value;
- else
+ } else {
result = ERROR_CANTREAD;
+ }
}
return result;
}
-LONG RegKey::ReadInt64(const wchar_t* name, int64_t* out_value) const {
+template <typename Reg>
+LONG GenericRegKey<Reg>::ReadInt64(const wchar_t* name,
+ int64_t* out_value) const {
DCHECK(out_value);
DWORD type = REG_QWORD;
int64_t local_value = 0;
@@ -347,16 +716,19 @@ LONG RegKey::ReadInt64(const wchar_t* name, int64_t* out_value) const {
LONG result = ReadValue(name, &local_value, &size, &type);
if (result == ERROR_SUCCESS) {
if ((type == REG_QWORD || type == REG_BINARY) &&
- size == sizeof(local_value))
+ size == sizeof(local_value)) {
*out_value = local_value;
- else
+ } else {
result = ERROR_CANTREAD;
+ }
}
return result;
}
-LONG RegKey::ReadValue(const wchar_t* name, std::wstring* out_value) const {
+template <typename Reg>
+LONG GenericRegKey<Reg>::ReadValue(const wchar_t* name,
+ std::wstring* out_value) const {
DCHECK(out_value);
const size_t kMaxStringLength = 1024; // This is after expansion.
// Use the one of the other forms of ReadValue if 1024 is too small for you.
@@ -386,36 +758,41 @@ LONG RegKey::ReadValue(const wchar_t* name, std::wstring* out_value) const {
return result;
}
-LONG RegKey::ReadValue(const wchar_t* name,
- void* data,
- DWORD* dsize,
- DWORD* dtype) const {
- LONG result = RegQueryValueEx(key_, name, nullptr, dtype,
+template <typename Reg>
+LONG GenericRegKey<Reg>::ReadValue(const wchar_t* name,
+ void* data,
+ DWORD* dsize,
+ DWORD* dtype) const {
+ LONG result = Reg::QueryValue(key_, name, nullptr, dtype,
reinterpret_cast<LPBYTE>(data), dsize);
return result;
}
-LONG RegKey::ReadValues(const wchar_t* name,
- std::vector<std::wstring>* values) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::ReadValues(const wchar_t* name,
+ std::vector<std::wstring>* values) {
values->clear();
DWORD type = REG_MULTI_SZ;
DWORD size = 0;
LONG result = ReadValue(name, nullptr, &size, &type);
- if (result != ERROR_SUCCESS || size == 0)
+ if (result != ERROR_SUCCESS || size == 0) {
return result;
+ }
- if (type != REG_MULTI_SZ)
+ if (type != REG_MULTI_SZ) {
return ERROR_CANTREAD;
+ }
std::vector<wchar_t> buffer(size / sizeof(wchar_t));
result = ReadValue(name, buffer.data(), &size, nullptr);
- if (result != ERROR_SUCCESS || size == 0)
+ if (result != ERROR_SUCCESS || size == 0) {
return result;
+ }
// Parse the double-null-terminated list of strings.
- // Note: This code is paranoid to not read outside of |buf|, in the case where
- // it may not be properly terminated.
+ // Note: This code is paranoid to not read outside of |buf|, in the case
+ // where it may not be properly terminated.
auto entry = buffer.cbegin();
auto buffer_end = buffer.cend();
while (entry < buffer_end && *entry != '\0') {
@@ -426,12 +803,15 @@ LONG RegKey::ReadValues(const wchar_t* name,
return 0;
}
-LONG RegKey::WriteValue(const wchar_t* name, DWORD in_value) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::WriteValue(const wchar_t* name, DWORD in_value) {
return WriteValue(name, &in_value, static_cast<DWORD>(sizeof(in_value)),
REG_DWORD);
}
-LONG RegKey::WriteValue(const wchar_t* name, const wchar_t* in_value) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::WriteValue(const wchar_t* name,
+ const wchar_t* in_value) {
return WriteValue(
name, in_value,
static_cast<DWORD>(sizeof(*in_value) *
@@ -439,49 +819,59 @@ LONG RegKey::WriteValue(const wchar_t* name, const wchar_t* in_value) {
REG_SZ);
}
-LONG RegKey::WriteValue(const wchar_t* name,
- const void* data,
- DWORD dsize,
- DWORD dtype) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::WriteValue(const wchar_t* name,
+ const void* data,
+ DWORD dsize,
+ DWORD dtype) {
DCHECK(data || !dsize);
LONG result =
- RegSetValueEx(key_, name, 0, dtype,
+ Reg::SetValue(key_, name, 0, dtype,
reinterpret_cast<LPBYTE>(const_cast<void*>(data)), dsize);
return result;
}
-bool RegKey::StartWatching(ChangeCallback callback) {
- if (!key_watcher_)
+template <typename Reg>
+bool GenericRegKey<Reg>::StartWatching(ChangeCallback callback) {
+ if (!key_watcher_) {
key_watcher_ = std::make_unique<Watcher>();
+ }
- if (!key_watcher_->StartWatching(key_, std::move(callback)))
+ if (!key_watcher_->StartWatching(key_, std::move(callback))) {
return false;
+ }
return true;
}
// static
-LONG RegKey::RegDelRecurse(HKEY root_key, const wchar_t* name, REGSAM access) {
+template <typename Reg>
+LONG GenericRegKey<Reg>::RegDelRecurse(HKEY root_key,
+ const wchar_t* name,
+ REGSAM access) {
// First, see if the key can be deleted without having to recurse.
- LONG result = RegDeleteKeyEx(root_key, name, access, 0);
- if (result == ERROR_SUCCESS)
+ LONG result = Reg::DeleteKey(root_key, name, access, 0);
+ if (result == ERROR_SUCCESS) {
return result;
+ }
HKEY target_key = nullptr;
- result = RegOpenKeyEx(root_key, name, 0, KEY_ENUMERATE_SUB_KEYS | access,
+ result = Reg::OpenKey(root_key, name, 0, KEY_ENUMERATE_SUB_KEYS | access,
&target_key);
- if (result == ERROR_FILE_NOT_FOUND)
+ if (result == ERROR_FILE_NOT_FOUND) {
return ERROR_SUCCESS;
+ }
if (result != ERROR_SUCCESS)
return result;
std::wstring subkey_name(name);
// Check for an ending slash and add one if it is missing.
- if (!subkey_name.empty() && subkey_name.back() != '\\')
+ if (!subkey_name.empty() && subkey_name.back() != '\\') {
subkey_name.push_back('\\');
+ }
// Enumerate the keys
result = ERROR_SUCCESS;
@@ -491,28 +881,70 @@ LONG RegKey::RegDelRecurse(HKEY root_key, const wchar_t* name, REGSAM access) {
while (result == ERROR_SUCCESS) {
DWORD key_size = kMaxKeyNameLength;
result =
- RegEnumKeyEx(target_key, 0, WriteInto(&key_name, kMaxKeyNameLength),
+ Reg::EnumKey(target_key, 0, WriteInto(&key_name, kMaxKeyNameLength),
&key_size, nullptr, nullptr, nullptr, nullptr);
- if (result != ERROR_SUCCESS)
+ if (result != ERROR_SUCCESS) {
break;
+ }
key_name.resize(key_size);
subkey_name.resize(base_key_length);
subkey_name += key_name;
- if (RegDelRecurse(root_key, subkey_name.c_str(), access) != ERROR_SUCCESS)
+ if (RegDelRecurse(root_key, subkey_name.c_str(), access) != ERROR_SUCCESS) {
break;
+ }
}
- RegCloseKey(target_key);
+ Reg::CloseKey(target_key);
// Try again to delete the key.
- result = RegDeleteKeyEx(root_key, name, access, 0);
+ result = Reg::DeleteKey(root_key, name, access, 0);
return result;
}
+// Instantiate the only two allowed versions of GenericRegKey for use by the
+// public base::win::RegKey and base::win::ExportDerivedRegKey.
+template class GenericRegKey<internal::Standard>;
+template class GenericRegKey<internal::ExportDerived>;
+
+} // namespace internal
+
+RegKey::RegKey() : GenericRegKey<internal::Standard>() {}
+RegKey::RegKey(HKEY key) : GenericRegKey<internal::Standard>(key) {}
+RegKey::RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access)
+ : GenericRegKey<internal::Standard>(rootkey, subkey, access) {}
+
+RegKey::RegKey(RegKey&& other) noexcept
+ : GenericRegKey<internal::Standard>(std::move(other)) {}
+RegKey& RegKey::operator=(RegKey&& other) {
+ GenericRegKey<internal::Standard>::operator=(std::move(other));
+ return *this;
+}
+
+RegKey::~RegKey() = default;
+
+ExportDerivedRegKey::ExportDerivedRegKey()
+ : GenericRegKey<internal::ExportDerived>() {}
+ExportDerivedRegKey::ExportDerivedRegKey(HKEY key)
+ : GenericRegKey<internal::ExportDerived>(key) {}
+ExportDerivedRegKey::ExportDerivedRegKey(HKEY rootkey,
+ const wchar_t* subkey,
+ REGSAM access)
+ : GenericRegKey<internal::ExportDerived>(rootkey, subkey, access) {}
+
+ExportDerivedRegKey::ExportDerivedRegKey(ExportDerivedRegKey&& other) noexcept
+ : GenericRegKey<internal::ExportDerived>(std::move(other)) {}
+ExportDerivedRegKey& ExportDerivedRegKey::operator=(
+ ExportDerivedRegKey&& other) {
+ GenericRegKey<internal::ExportDerived>::operator=(std::move(other));
+ return *this;
+}
+
+ExportDerivedRegKey::~ExportDerivedRegKey() = default;
+
// RegistryValueIterator ------------------------------------------------------
RegistryValueIterator::RegistryValueIterator(HKEY root_key,
diff --git a/base/win/registry.h b/base/win/registry.h
index 8037cedfc..8e6372a40 100644
--- a/base/win/registry.h
+++ b/base/win/registry.h
@@ -13,8 +13,11 @@
#include "base/base_export.h"
#include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
#include "base/win/windows_types.h"
+class ShellUtil;
+
namespace base {
namespace win {
@@ -27,21 +30,31 @@ namespace win {
// is not touched in case of failure.
// * Functions returning LONG indicate success as ERROR_SUCCESS or an
// error as a (non-zero) win32 error code.
-class BASE_EXPORT RegKey {
+//
+// Most developers should use base::win::RegKey subclass below.
+namespace internal {
+
+class Standard;
+class ExportDerived;
+template <typename T>
+class RegTestTraits;
+
+template <typename Reg>
+class GenericRegKey {
public:
// Called from the MessageLoop when the key changes.
using ChangeCallback = OnceCallback<void()>;
- RegKey();
- explicit RegKey(HKEY key);
- RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access);
- RegKey(RegKey&& other) noexcept;
- RegKey& operator=(RegKey&& other);
+ GenericRegKey();
+ explicit GenericRegKey(HKEY key);
+ GenericRegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access);
+ GenericRegKey(GenericRegKey&& other) noexcept;
+ GenericRegKey& operator=(GenericRegKey&& other);
- RegKey(const RegKey&) = delete;
- RegKey& operator=(const RegKey&) = delete;
+ GenericRegKey(const GenericRegKey&) = delete;
+ GenericRegKey& operator=(const GenericRegKey&) = delete;
- ~RegKey();
+ virtual ~GenericRegKey();
LONG Create(HKEY rootkey, const wchar_t* subkey, REGSAM access);
@@ -139,8 +152,8 @@ class BASE_EXPORT RegKey {
// Starts watching the key to see if any of its values have changed.
// The key must have been opened with the KEY_NOTIFY access privilege.
// Returns true on success.
- // To stop watching, delete this RegKey object. To continue watching the
- // object after the callback is invoked, call StartWatching again.
+ // To stop watching, delete this GenericRegKey object. To continue watching
+ // the object after the callback is invoked, call StartWatching again.
bool StartWatching(ChangeCallback callback);
HKEY Handle() const { return key_; }
@@ -156,6 +169,47 @@ class BASE_EXPORT RegKey {
std::unique_ptr<Watcher> key_watcher_;
};
+} // namespace internal
+
+// The Windows registry utility class most developers should use.
+class BASE_EXPORT RegKey : public internal::GenericRegKey<internal::Standard> {
+ public:
+ RegKey();
+ explicit RegKey(HKEY key);
+ RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access);
+ RegKey(RegKey&& other) noexcept;
+ RegKey& operator=(RegKey&& other);
+
+ RegKey(const RegKey&) = delete;
+ RegKey& operator=(const RegKey&) = delete;
+
+ ~RegKey() override;
+};
+
+// A Windows registry class that derives its calls directly from advapi32.dll.
+// Generally, you should use RegKey above. Note that use of this API will pin
+// advapi32.dll. If you need to use this class, please reach out to the
+// base/win/OWNERS first.
+class BASE_EXPORT ExportDerivedRegKey
+ : public internal::GenericRegKey<internal::ExportDerived> {
+ public:
+ ExportDerivedRegKey(ExportDerivedRegKey&& other) noexcept;
+ ExportDerivedRegKey& operator=(ExportDerivedRegKey&& other);
+
+ ExportDerivedRegKey(const ExportDerivedRegKey&) = delete;
+ ExportDerivedRegKey& operator=(const ExportDerivedRegKey&) = delete;
+
+ ~ExportDerivedRegKey() override;
+
+ private:
+ friend class ::ShellUtil;
+ friend class internal::RegTestTraits<ExportDerivedRegKey>;
+
+ ExportDerivedRegKey();
+ explicit ExportDerivedRegKey(HKEY key);
+ ExportDerivedRegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access);
+};
+
// Iterates the entries found in a particular folder on the registry.
class BASE_EXPORT RegistryValueIterator {
public:
diff --git a/base/win/registry_unittest.cc b/base/win/registry_unittest.cc
index 77bb741d1..030d01e02 100644
--- a/base/win/registry_unittest.cc
+++ b/base/win/registry_unittest.cc
@@ -11,6 +11,7 @@
#include <cstring>
#include <iterator>
+#include <type_traits>
#include <utility>
#include "base/compiler_specific.h"
@@ -37,6 +38,7 @@ constexpr wchar_t kRootKey[] = L"Base_Registry_Unittest";
// A test harness for registry tests that operate in HKCU. Each test is given
// a valid key distinct from that used by other tests.
+template <typename Traits>
class RegistryTest : public testing::Test {
protected:
RegistryTest() : root_key_(std::wstring(L"Software\\") + kRootKey) {}
@@ -46,7 +48,8 @@ class RegistryTest : public testing::Test {
registry_override_.OverrideRegistry(HKEY_CURRENT_USER));
// Create the test's root key.
- RegKey key(HKEY_CURRENT_USER, L"", KEY_CREATE_SUB_KEY);
+ typename Traits::RegType key(
+ Traits::Create(HKEY_CURRENT_USER, L"", KEY_CREATE_SUB_KEY));
ASSERT_NE(ERROR_SUCCESS,
key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ));
ASSERT_EQ(ERROR_SUCCESS,
@@ -62,10 +65,50 @@ class RegistryTest : public testing::Test {
const std::wstring root_key_;
};
-TEST_F(RegistryTest, ValueTest) {
- RegKey key;
+} // namespace
+
+namespace internal {
+
+template <typename T>
+class RegTestTraits {
+ public:
+ using RegType = T;
+
+ static T Create() { return T(); }
+
+ static T Create(HKEY rootkey, const wchar_t* subkey, REGSAM access) {
+ return T(rootkey, subkey, access);
+ }
+};
+
+} // namespace internal
+
+namespace {
+
+class RegistryTypeNames {
+ public:
+ template <typename T>
+ static std::string GetName(int index) {
+ if (std::is_same<typename T::RegType, RegKey>()) {
+ return "RegKey";
+ }
+ if (std::is_same<typename T::RegType, ExportDerivedRegKey>()) {
+ return "ExportDerivedRegKey";
+ }
+ }
+};
+
+} // namespace
+
+using RegistryTypes =
+ ::testing::Types<internal::RegTestTraits<RegKey>,
+ internal::RegTestTraits<ExportDerivedRegKey>>;
+TYPED_TEST_SUITE(RegistryTest, RegistryTypes, RegistryTypeNames);
+
+TYPED_TEST(RegistryTest, ValueTest) {
+ typename TypeParam::RegType key(TypeParam::Create());
- ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(),
+ ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(),
KEY_READ | KEY_SET_VALUE));
ASSERT_TRUE(key.Valid());
@@ -116,9 +159,9 @@ TEST_F(RegistryTest, ValueTest) {
EXPECT_FALSE(key.HasValue(kInt64ValueName));
}
-TEST_F(RegistryTest, BigValueIteratorTest) {
- RegKey key;
- ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(),
+TYPED_TEST(RegistryTest, BigValueIteratorTest) {
+ typename TypeParam::RegType key(TypeParam::Create());
+ ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(),
KEY_READ | KEY_SET_VALUE));
ASSERT_TRUE(key.Valid());
@@ -127,7 +170,7 @@ TEST_F(RegistryTest, BigValueIteratorTest) {
ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(data.c_str(), data.c_str()));
- RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str());
+ RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str());
ASSERT_TRUE(iterator.Valid());
EXPECT_EQ(data, iterator.Name());
EXPECT_EQ(data, iterator.Value());
@@ -137,9 +180,9 @@ TEST_F(RegistryTest, BigValueIteratorTest) {
EXPECT_FALSE(iterator.Valid());
}
-TEST_F(RegistryTest, TruncatedCharTest) {
- RegKey key;
- ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(),
+TYPED_TEST(RegistryTest, TruncatedCharTest) {
+ typename TypeParam::RegType key(TypeParam::Create());
+ ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(),
KEY_READ | KEY_SET_VALUE));
ASSERT_TRUE(key.Valid());
@@ -150,7 +193,7 @@ TEST_F(RegistryTest, TruncatedCharTest) {
ASSERT_EQ(ERROR_SUCCESS,
key.WriteValue(kName, kData, std::size(kData), REG_BINARY));
- RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str());
+ RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str());
ASSERT_TRUE(iterator.Valid());
// Avoid having to use EXPECT_STREQ here by leveraging StringPiece's
// operator== to perform a deep comparison.
@@ -167,19 +210,20 @@ TEST_F(RegistryTest, TruncatedCharTest) {
}
// Tests that the value iterator is okay with an empty key.
-TEST_F(RegistryTest, ValueIteratorEmptyKey) {
- RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str());
+TYPED_TEST(RegistryTest, ValueIteratorEmptyKey) {
+ RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str());
EXPECT_EQ(iterator.ValueCount(), 0U);
EXPECT_FALSE(iterator.Valid());
}
// Tests that the default value is seen by a value iterator.
-TEST_F(RegistryTest, ValueIteratorDefaultValue) {
+TYPED_TEST(RegistryTest, ValueIteratorDefaultValue) {
const WStringPiece kTestString(L"i miss you");
- ASSERT_EQ(RegKey(HKEY_CURRENT_USER, root_key().c_str(), KEY_SET_VALUE)
+ ASSERT_EQ(TypeParam::Create(HKEY_CURRENT_USER, this->root_key().c_str(),
+ KEY_SET_VALUE)
.WriteValue(nullptr, kTestString.data()),
ERROR_SUCCESS);
- RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str());
+ RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str());
EXPECT_EQ(iterator.ValueCount(), 1U);
ASSERT_TRUE(iterator.Valid());
EXPECT_EQ(WStringPiece(iterator.Name()), WStringPiece());
@@ -190,8 +234,8 @@ TEST_F(RegistryTest, ValueIteratorDefaultValue) {
EXPECT_FALSE(iterator.Valid());
}
-TEST_F(RegistryTest, RecursiveDelete) {
- RegKey key;
+TYPED_TEST(RegistryTest, RecursiveDelete) {
+ typename TypeParam::RegType key(TypeParam::Create());
// Create root_key()
// \->Bar (TestValue)
// \->Foo (TestValue)
@@ -200,7 +244,7 @@ TEST_F(RegistryTest, RecursiveDelete) {
// \->Moo
// \->Foo
// and delete root_key()
- std::wstring key_path = root_key();
+ std::wstring key_path = this->root_key();
ASSERT_EQ(ERROR_SUCCESS,
key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_CREATE_SUB_KEY));
ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Bar", KEY_WRITE));
@@ -222,7 +266,7 @@ TEST_F(RegistryTest, RecursiveDelete) {
key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ));
ASSERT_EQ(ERROR_SUCCESS,
- key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_WRITE));
+ key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_WRITE));
ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L""));
ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"Bar\\Foo"));
ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"Bar"));
@@ -239,33 +283,35 @@ TEST_F(RegistryTest, RecursiveDelete) {
key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ));
ASSERT_EQ(ERROR_SUCCESS,
- key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_WRITE));
+ key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_WRITE));
ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(L"Bar"));
ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(L"Bar"));
ASSERT_NE(ERROR_SUCCESS,
key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ));
}
-TEST_F(RegistryTest, OpenSubKey) {
- RegKey key;
- ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(),
+TYPED_TEST(RegistryTest, OpenSubKey) {
+ typename TypeParam::RegType key(TypeParam::Create());
+ ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(),
KEY_READ | KEY_CREATE_SUB_KEY));
ASSERT_NE(ERROR_SUCCESS, key.OpenKey(L"foo", KEY_READ));
ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"foo", KEY_READ));
ASSERT_EQ(ERROR_SUCCESS,
- key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ));
+ key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ));
ASSERT_EQ(ERROR_SUCCESS, key.OpenKey(L"foo", KEY_READ));
- std::wstring foo_key = root_key() + L"\\Foo";
+ std::wstring foo_key = this->root_key() + L"\\Foo";
ASSERT_EQ(ERROR_SUCCESS,
key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_READ));
ASSERT_EQ(ERROR_SUCCESS,
- key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_WRITE));
+ key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_WRITE));
ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(L"foo"));
}
+namespace {
+
class TestChangeDelegate {
public:
TestChangeDelegate() = default;
@@ -286,22 +332,25 @@ class TestChangeDelegate {
bool called_ = false;
};
-TEST_F(RegistryTest, ChangeCallback) {
- RegKey key;
+} // namespace
+
+TYPED_TEST(RegistryTest, ChangeCallback) {
+ typename TypeParam::RegType key(TypeParam::Create());
TestChangeDelegate delegate;
test::TaskEnvironment task_environment;
ASSERT_EQ(ERROR_SUCCESS,
- key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ));
+ key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ));
ASSERT_TRUE(key.StartWatching(
BindOnce(&TestChangeDelegate::OnKeyChanged, Unretained(&delegate))));
EXPECT_FALSE(delegate.WasCalled());
// Make some change.
- RegKey key2;
- ASSERT_EQ(ERROR_SUCCESS, key2.Open(HKEY_CURRENT_USER, root_key().c_str(),
- KEY_READ | KEY_SET_VALUE));
+ typename TypeParam::RegType key2(TypeParam::Create());
+ ASSERT_EQ(ERROR_SUCCESS,
+ key2.Open(HKEY_CURRENT_USER, this->root_key().c_str(),
+ KEY_READ | KEY_SET_VALUE));
ASSERT_TRUE(key2.Valid());
EXPECT_EQ(ERROR_SUCCESS, key2.WriteValue(L"name", L"data"));
@@ -346,20 +395,21 @@ class RegistryWatcherThread : public SimpleThread {
} // namespace
-TEST_F(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) {
- RegKey key;
+TYPED_TEST(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) {
+ typename TypeParam::RegType key(TypeParam::Create());
- ASSERT_EQ(key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ),
+ ASSERT_EQ(key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ),
ERROR_SUCCESS);
auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::TestMockTimeTaskRunner::Type::kBoundToThread);
- ::testing::StrictMock<base::MockCallback<base::win::RegKey::ChangeCallback>>
+ ::testing::StrictMock<
+ base::MockCallback<typename TypeParam::RegType::ChangeCallback>>
change_cb;
- test_task_runner->PostTask(FROM_HERE,
- BindOnce(IgnoreResult(&RegKey::StartWatching),
- Unretained(&key), change_cb.Get()));
+ test_task_runner->PostTask(
+ FROM_HERE, BindOnce(IgnoreResult(&TypeParam::RegType::StartWatching),
+ Unretained(&key), change_cb.Get()));
{
// Start the watch on a thread that then goes away.
@@ -379,8 +429,8 @@ TEST_F(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) {
EXPECT_CALL(change_cb, Run).WillOnce([&run_loop]() { run_loop.Quit(); });
// Make some change.
- RegKey key2;
- ASSERT_EQ(key2.Open(HKEY_CURRENT_USER, root_key().c_str(),
+ typename TypeParam::RegType key2(TypeParam::Create());
+ ASSERT_EQ(key2.Open(HKEY_CURRENT_USER, this->root_key().c_str(),
KEY_READ | KEY_SET_VALUE),
ERROR_SUCCESS);
ASSERT_TRUE(key2.Valid());
@@ -390,12 +440,13 @@ TEST_F(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) {
run_loop.Run();
}
-TEST_F(RegistryTest, TestMoveConstruct) {
- RegKey key;
+TYPED_TEST(RegistryTest, TestMoveConstruct) {
+ typename TypeParam::RegType key(TypeParam::Create());
- ASSERT_EQ(key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_SET_VALUE),
- ERROR_SUCCESS);
- RegKey key2(std::move(key));
+ ASSERT_EQ(
+ key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_SET_VALUE),
+ ERROR_SUCCESS);
+ typename TypeParam::RegType key2(std::move(key));
// The old key should be meaningless now.
EXPECT_EQ(key.Handle(), nullptr);
@@ -405,17 +456,17 @@ TEST_F(RegistryTest, TestMoveConstruct) {
EXPECT_EQ(key2.WriteValue(L"foo", 1U), ERROR_SUCCESS);
}
-TEST_F(RegistryTest, TestMoveAssign) {
- RegKey key;
- RegKey key2;
+TYPED_TEST(RegistryTest, TestMoveAssign) {
+ typename TypeParam::RegType key(TypeParam::Create());
+ typename TypeParam::RegType key2(TypeParam::Create());
const wchar_t kFooValueName[] = L"foo";
- ASSERT_EQ(key.Open(HKEY_CURRENT_USER, root_key().c_str(),
+ ASSERT_EQ(key.Open(HKEY_CURRENT_USER, this->root_key().c_str(),
KEY_SET_VALUE | KEY_QUERY_VALUE),
ERROR_SUCCESS);
ASSERT_EQ(key.WriteValue(kFooValueName, 1U), ERROR_SUCCESS);
- ASSERT_EQ(key2.Create(HKEY_CURRENT_USER, (root_key() + L"\\child").c_str(),
- KEY_SET_VALUE),
+ ASSERT_EQ(key2.Create(HKEY_CURRENT_USER,
+ (this->root_key() + L"\\child").c_str(), KEY_SET_VALUE),
ERROR_SUCCESS);
key2 = std::move(key);
@@ -431,8 +482,8 @@ TEST_F(RegistryTest, TestMoveAssign) {
// Verify that either the platform, or the API-integration, causes deletion
// attempts via an invalid handle to fail with the expected error code.
-TEST_F(RegistryTest, DeleteWithInvalidRegKey) {
- RegKey key;
+TYPED_TEST(RegistryTest, DeleteWithInvalidRegKey) {
+ typename TypeParam::RegType key(TypeParam::Create());
static const wchar_t kFooName[] = L"foo";
@@ -441,9 +492,12 @@ TEST_F(RegistryTest, DeleteWithInvalidRegKey) {
EXPECT_EQ(key.DeleteValue(kFooName), ERROR_INVALID_HANDLE);
}
+namespace {
+
// A test harness for tests that use HKLM to test WoW redirection and such.
// TODO(https://crbug.com/377917): The tests here that write to the registry are
// disabled because they need work to handle parallel runs of different tests.
+template <typename Traits>
class RegistryTestHKLM : public ::testing::Test {
protected:
enum : REGSAM {
@@ -470,59 +524,71 @@ class RegistryTestHKLM : public ::testing::Test {
const std::wstring foo_software_key_;
};
-class RegistryTestHKLMAdmin : public RegistryTestHKLM {
+} // namespace
+
+TYPED_TEST_SUITE(RegistryTestHKLM, RegistryTypes, RegistryTypeNames);
+
+namespace {
+
+template <typename Traits>
+class RegistryTestHKLMAdmin : public RegistryTestHKLM<Traits> {
protected:
void SetUp() override {
- if (!IsRedirectorPresent()) {
+ if (!this->IsRedirectorPresent()) {
GTEST_SKIP();
}
if (!::IsUserAnAdmin()) {
GTEST_SKIP();
}
// Clean up any stale registry keys.
- for (const REGSAM mask : {kNativeViewMask, kRedirectedViewMask}) {
- RegKey key;
+ for (const REGSAM mask :
+ {this->kNativeViewMask, this->kRedirectedViewMask}) {
+ typename Traits::RegType key(Traits::Create());
key.Open(HKEY_LOCAL_MACHINE, L"Software", KEY_SET_VALUE | mask);
key.DeleteKey(kRootKey);
}
}
};
+} // namespace
+
+TYPED_TEST_SUITE(RegistryTestHKLMAdmin, RegistryTypes, RegistryTypeNames);
+
// This test requires running as an Administrator as it tests redirected
// registry writes to HKLM\Software
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa384253.aspx
-TEST_F(RegistryTestHKLMAdmin, Wow64RedirectedFromNative) {
- RegKey key;
+TYPED_TEST(RegistryTestHKLMAdmin, Wow64RedirectedFromNative) {
+ typename TypeParam::RegType key(TypeParam::Create());
// Test redirected key access from non-redirected.
ASSERT_EQ(ERROR_SUCCESS,
- key.Create(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(),
- KEY_WRITE | kRedirectedViewMask));
- ASSERT_NE(ERROR_SUCCESS,
- key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), KEY_READ));
+ key.Create(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(),
+ KEY_WRITE | this->kRedirectedViewMask));
+ ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE,
+ this->foo_software_key_.c_str(), KEY_READ));
ASSERT_NE(ERROR_SUCCESS,
- key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(),
- KEY_READ | kNativeViewMask));
+ key.Open(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(),
+ KEY_READ | this->kNativeViewMask));
// Open the non-redirected view of the parent and try to delete the test key.
ASSERT_EQ(ERROR_SUCCESS,
key.Open(HKEY_LOCAL_MACHINE, L"Software", KEY_SET_VALUE));
ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey));
ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software",
- KEY_SET_VALUE | kNativeViewMask));
+ KEY_SET_VALUE | this->kNativeViewMask));
ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey));
// Open the redirected view and delete the key created above.
ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software",
- KEY_SET_VALUE | kRedirectedViewMask));
+ KEY_SET_VALUE | this->kRedirectedViewMask));
ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey));
}
// Test for the issue found in http://crbug.com/384587 where OpenKey would call
// Close() and reset wow64_access_ flag to 0 and cause a NOTREACHED to hit on a
// subsequent OpenKey call.
-TEST_F(RegistryTestHKLM, SameWowFlags) {
- RegKey key;
+TYPED_TEST(RegistryTestHKLM, SameWowFlags) {
+ typename TypeParam::RegType key(TypeParam::Create());
ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software",
KEY_READ | KEY_WOW64_64KEY));
@@ -531,30 +597,28 @@ TEST_F(RegistryTestHKLM, SameWowFlags) {
ASSERT_EQ(ERROR_SUCCESS, key.OpenKey(L"Windows", KEY_READ | KEY_WOW64_64KEY));
}
-TEST_F(RegistryTestHKLMAdmin, Wow64NativeFromRedirected) {
- RegKey key;
+TYPED_TEST(RegistryTestHKLMAdmin, Wow64NativeFromRedirected) {
+ typename TypeParam::RegType key(TypeParam::Create());
// Test non-redirected key access from redirected.
ASSERT_EQ(ERROR_SUCCESS,
- key.Create(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(),
- KEY_WRITE | kNativeViewMask));
- ASSERT_EQ(ERROR_SUCCESS,
- key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), KEY_READ));
+ key.Create(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(),
+ KEY_WRITE | this->kNativeViewMask));
+ ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE,
+ this->foo_software_key_.c_str(), KEY_READ));
ASSERT_NE(ERROR_SUCCESS,
- key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(),
- KEY_READ | kRedirectedViewMask));
+ key.Open(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(),
+ KEY_READ | this->kRedirectedViewMask));
// Open the redirected view of the parent and try to delete the test key
// from the non-redirected view.
ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software",
- KEY_SET_VALUE | kRedirectedViewMask));
+ KEY_SET_VALUE | this->kRedirectedViewMask));
ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey));
ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software",
- KEY_SET_VALUE | kNativeViewMask));
+ KEY_SET_VALUE | this->kNativeViewMask));
ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey));
}
-} // namespace
-
} // namespace base::win
diff --git a/base/win/scoped_co_mem.h b/base/win/scoped_co_mem.h
index 078290094..0667fbc7e 100644
--- a/base/win/scoped_co_mem.h
+++ b/base/win/scoped_co_mem.h
@@ -8,6 +8,7 @@
#include <objbase.h>
#include "base/check.h"
+#include "base/memory/raw_ptr_exclusion.h"
namespace base {
namespace win {
@@ -54,7 +55,9 @@ class ScopedCoMem {
T* get() const { return mem_ptr_; }
private:
- T* mem_ptr_;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #addr-of, #union
+ RAW_PTR_EXCLUSION T* mem_ptr_;
};
} // namespace win
diff --git a/base/win/scoped_safearray.h b/base/win/scoped_safearray.h
index a7303fee7..d83fdf998 100644
--- a/base/win/scoped_safearray.h
+++ b/base/win/scoped_safearray.h
@@ -9,6 +9,7 @@
#include "base/base_export.h"
#include "base/check_op.h"
+#include "base/memory/raw_ptr_exclusion.h"
#include "base/win/variant_conversions.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
@@ -106,7 +107,9 @@ class BASE_EXPORT ScopedSafearray {
array_size_ = 0U;
}
- SAFEARRAY* safearray_ = nullptr;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter
+ // for: #union
+ RAW_PTR_EXCLUSION SAFEARRAY* safearray_ = nullptr;
VARTYPE vartype_ = VT_EMPTY;
pointer array_ = nullptr;
size_t array_size_ = 0U;
@@ -218,7 +221,9 @@ class BASE_EXPORT ScopedSafearray {
bool operator!=(const ScopedSafearray& safearray2) const = delete;
private:
- SAFEARRAY* safearray_;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #addr-of
+ RAW_PTR_EXCLUSION SAFEARRAY* safearray_;
};
} // namespace win