• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/allocator/partition_alloc_features.h"
6 
7 #include "base/allocator/miracle_parameter.h"
8 #include "base/allocator/partition_allocator/src/partition_alloc/partition_alloc_base/time/time.h"
9 #include "base/allocator/partition_allocator/src/partition_alloc/partition_alloc_buildflags.h"
10 #include "base/allocator/partition_allocator/src/partition_alloc/partition_root.h"
11 #include "base/allocator/partition_allocator/src/partition_alloc/thread_cache.h"
12 #include "base/base_export.h"
13 #include "base/feature_list.h"
14 #include "base/features.h"
15 #include "base/metrics/field_trial_params.h"
16 #include "base/time/time.h"
17 #include "build/build_config.h"
18 #include "build/chromecast_buildflags.h"
19 #include "build/chromeos_buildflags.h"
20 
21 namespace base {
22 namespace features {
23 
24 BASE_FEATURE(kPartitionAllocUnretainedDanglingPtr,
25              "PartitionAllocUnretainedDanglingPtr",
26              FEATURE_ENABLED_BY_DEFAULT);
27 
28 constexpr FeatureParam<UnretainedDanglingPtrMode>::Option
29     kUnretainedDanglingPtrModeOption[] = {
30         {UnretainedDanglingPtrMode::kCrash, "crash"},
31         {UnretainedDanglingPtrMode::kDumpWithoutCrashing,
32          "dump_without_crashing"},
33 };
34 const base::FeatureParam<UnretainedDanglingPtrMode>
35     kUnretainedDanglingPtrModeParam = {
36         &kPartitionAllocUnretainedDanglingPtr,
37         "mode",
38         UnretainedDanglingPtrMode::kDumpWithoutCrashing,
39         &kUnretainedDanglingPtrModeOption,
40 };
41 
42 BASE_FEATURE(kPartitionAllocDanglingPtr,
43              "PartitionAllocDanglingPtr",
44 #if BUILDFLAG(ENABLE_DANGLING_RAW_PTR_FEATURE_FLAG) ||                   \
45     (BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS) && BUILDFLAG(IS_LINUX) && \
46      !defined(OFFICIAL_BUILD) && (!defined(NDEBUG) || DCHECK_IS_ON()))
47              FEATURE_ENABLED_BY_DEFAULT
48 #else
49              FEATURE_DISABLED_BY_DEFAULT
50 #endif
51 );
52 
53 constexpr FeatureParam<DanglingPtrMode>::Option kDanglingPtrModeOption[] = {
54     {DanglingPtrMode::kCrash, "crash"},
55     {DanglingPtrMode::kLogOnly, "log_only"},
56 };
57 const base::FeatureParam<DanglingPtrMode> kDanglingPtrModeParam{
58     &kPartitionAllocDanglingPtr,
59     "mode",
60     DanglingPtrMode::kCrash,
61     &kDanglingPtrModeOption,
62 };
63 constexpr FeatureParam<DanglingPtrType>::Option kDanglingPtrTypeOption[] = {
64     {DanglingPtrType::kAll, "all"},
65     {DanglingPtrType::kCrossTask, "cross_task"},
66 };
67 const base::FeatureParam<DanglingPtrType> kDanglingPtrTypeParam{
68     &kPartitionAllocDanglingPtr,
69     "type",
70     DanglingPtrType::kAll,
71     &kDanglingPtrTypeOption,
72 };
73 
74 #if BUILDFLAG(USE_STARSCAN)
75 // If enabled, PCScan is turned on by default for all partitions that don't
76 // disable it explicitly.
77 BASE_FEATURE(kPartitionAllocPCScan,
78              "PartitionAllocPCScan",
79              FEATURE_DISABLED_BY_DEFAULT);
80 #endif  // BUILDFLAG(USE_STARSCAN)
81 
82 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
83 // If enabled, PCScan is turned on only for the browser's malloc partition.
84 BASE_FEATURE(kPartitionAllocPCScanBrowserOnly,
85              "PartitionAllocPCScanBrowserOnly",
86              FEATURE_DISABLED_BY_DEFAULT);
87 
88 // If enabled, PCScan is turned on only for the renderer's malloc partition.
89 BASE_FEATURE(kPartitionAllocPCScanRendererOnly,
90              "PartitionAllocPCScanRendererOnly",
91              FEATURE_DISABLED_BY_DEFAULT);
92 
93 // Use a larger maximum thread cache cacheable bucket size.
94 BASE_FEATURE(kPartitionAllocLargeThreadCacheSize,
95              "PartitionAllocLargeThreadCacheSize",
96              FEATURE_ENABLED_BY_DEFAULT);
97 
98 MIRACLE_PARAMETER_FOR_INT(
99     GetPartitionAllocLargeThreadCacheSizeValue,
100     kPartitionAllocLargeThreadCacheSize,
101     "PartitionAllocLargeThreadCacheSizeValue",
102     ::partition_alloc::ThreadCacheLimits::kLargeSizeThreshold)
103 
104 MIRACLE_PARAMETER_FOR_INT(
105     GetPartitionAllocLargeThreadCacheSizeValueForLowRAMAndroid,
106     kPartitionAllocLargeThreadCacheSize,
107     "PartitionAllocLargeThreadCacheSizeValueForLowRAMAndroid",
108     ::partition_alloc::ThreadCacheLimits::kDefaultSizeThreshold)
109 
110 BASE_FEATURE(kPartitionAllocLargeEmptySlotSpanRing,
111              "PartitionAllocLargeEmptySlotSpanRing",
112              FEATURE_DISABLED_BY_DEFAULT);
113 
114 BASE_FEATURE(kPartitionAllocSchedulerLoopQuarantine,
115              "PartitionAllocSchedulerLoopQuarantine",
116              FEATURE_DISABLED_BY_DEFAULT);
117 // Scheduler Loop Quarantine's capacity in bytes.
118 const base::FeatureParam<int> kPartitionAllocSchedulerLoopQuarantineCapacity{
119     &kPartitionAllocSchedulerLoopQuarantine,
120     "PartitionAllocSchedulerLoopQuarantineCapacity", 0};
121 
122 BASE_FEATURE(kPartitionAllocZappingByFreeFlags,
123              "PartitionAllocZappingByFreeFlags",
124              FEATURE_DISABLED_BY_DEFAULT);
125 #endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
126 
127 BASE_FEATURE(kPartitionAllocBackupRefPtr,
128              "PartitionAllocBackupRefPtr",
129 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \
130     BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) ||     \
131     (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CASTOS)) ||                  \
132     BUILDFLAG(ENABLE_BACKUP_REF_PTR_FEATURE_FLAG)
133              FEATURE_ENABLED_BY_DEFAULT
134 #else
135              FEATURE_DISABLED_BY_DEFAULT
136 #endif
137 );
138 
139 BASE_EXPORT BASE_DECLARE_FEATURE(kPartitionAllocBackupRefPtrForAsh);
140 BASE_FEATURE(kPartitionAllocBackupRefPtrForAsh,
141              "PartitionAllocBackupRefPtrForAsh",
142              FEATURE_ENABLED_BY_DEFAULT);
143 
144 constexpr FeatureParam<BackupRefPtrEnabledProcesses>::Option
145     kBackupRefPtrEnabledProcessesOptions[] = {
146         {BackupRefPtrEnabledProcesses::kBrowserOnly, "browser-only"},
147         {BackupRefPtrEnabledProcesses::kBrowserAndRenderer,
148          "browser-and-renderer"},
149         {BackupRefPtrEnabledProcesses::kNonRenderer, "non-renderer"},
150         {BackupRefPtrEnabledProcesses::kAllProcesses, "all-processes"}};
151 
152 const base::FeatureParam<BackupRefPtrEnabledProcesses>
153     kBackupRefPtrEnabledProcessesParam{
154         &kPartitionAllocBackupRefPtr, "enabled-processes",
155         BackupRefPtrEnabledProcesses::kNonRenderer,
156         &kBackupRefPtrEnabledProcessesOptions};
157 
158 constexpr FeatureParam<BackupRefPtrRefCountSize>::Option
159     kBackupRefPtrRefCountSizeOptions[] = {
160         {BackupRefPtrRefCountSize::kNatural, "natural"},
161         {BackupRefPtrRefCountSize::k4B, "4B"},
162         {BackupRefPtrRefCountSize::k8B, "8B"},
163         {BackupRefPtrRefCountSize::k16B, "16B"}};
164 
165 const base::FeatureParam<BackupRefPtrRefCountSize>
166     kBackupRefPtrRefCountSizeParam{
167         &kPartitionAllocBackupRefPtr, "ref-count-size",
168         BackupRefPtrRefCountSize::kNatural, &kBackupRefPtrRefCountSizeOptions};
169 
170 // Map -with-memory-reclaimer modes onto their counterpars without the suffix.
171 // They are the same, as memory reclaimer is now controlled independently.
172 // However, we need to keep both option strings, as there is a long tail of
173 // clients that may have an old field trial config, which used these modes.
174 //
175 // DO NOT USE -with-memory-reclaimer modes in new configs!
176 constexpr FeatureParam<BackupRefPtrMode>::Option kBackupRefPtrModeOptions[] = {
177     {BackupRefPtrMode::kDisabled, "disabled"},
178     {BackupRefPtrMode::kEnabled, "enabled"},
179     {BackupRefPtrMode::kEnabled, "enabled-with-memory-reclaimer"},
180     {BackupRefPtrMode::kDisabledButSplitPartitions2Way,
181      "disabled-but-2-way-split"},
182     {BackupRefPtrMode::kDisabledButSplitPartitions2Way,
183      "disabled-but-2-way-split-with-memory-reclaimer"},
184     {BackupRefPtrMode::kDisabledButSplitPartitions3Way,
185      "disabled-but-3-way-split"},
186 };
187 
188 const base::FeatureParam<BackupRefPtrMode> kBackupRefPtrModeParam{
189     &kPartitionAllocBackupRefPtr, "brp-mode", BackupRefPtrMode::kEnabled,
190     &kBackupRefPtrModeOptions};
191 
192 BASE_FEATURE(kPartitionAllocMemoryTagging,
193              "PartitionAllocMemoryTagging",
194              FEATURE_DISABLED_BY_DEFAULT);
195 
196 constexpr FeatureParam<MemtagMode>::Option kMemtagModeOptions[] = {
197     {MemtagMode::kSync, "sync"},
198     {MemtagMode::kAsync, "async"}};
199 
200 const base::FeatureParam<MemtagMode> kMemtagModeParam{
201     &kPartitionAllocMemoryTagging, "memtag-mode", MemtagMode::kAsync,
202     &kMemtagModeOptions};
203 
204 constexpr FeatureParam<MemoryTaggingEnabledProcesses>::Option
205     kMemoryTaggingEnabledProcessesOptions[] = {
206         {MemoryTaggingEnabledProcesses::kBrowserOnly, "browser-only"},
207         {MemoryTaggingEnabledProcesses::kNonRenderer, "non-renderer"},
208         {MemoryTaggingEnabledProcesses::kAllProcesses, "all-processes"}};
209 
210 const base::FeatureParam<MemoryTaggingEnabledProcesses>
211     kMemoryTaggingEnabledProcessesParam{
212         &kPartitionAllocMemoryTagging, "enabled-processes",
213         MemoryTaggingEnabledProcesses::kBrowserOnly,
214         &kMemoryTaggingEnabledProcessesOptions};
215 
216 BASE_FEATURE(kKillPartitionAllocMemoryTagging,
217              "KillPartitionAllocMemoryTagging",
218              FEATURE_DISABLED_BY_DEFAULT);
219 
220 BASE_EXPORT BASE_DECLARE_FEATURE(kPartitionAllocPermissiveMte);
221 BASE_FEATURE(kPartitionAllocPermissiveMte,
222              "PartitionAllocPermissiveMte",
223              FEATURE_ENABLED_BY_DEFAULT);
224 
225 const base::FeatureParam<bool> kBackupRefPtrAsanEnableDereferenceCheckParam{
226     &kPartitionAllocBackupRefPtr, "asan-enable-dereference-check", true};
227 const base::FeatureParam<bool> kBackupRefPtrAsanEnableExtractionCheckParam{
228     &kPartitionAllocBackupRefPtr, "asan-enable-extraction-check",
229     false};  // Not much noise at the moment to enable by default.
230 const base::FeatureParam<bool> kBackupRefPtrAsanEnableInstantiationCheckParam{
231     &kPartitionAllocBackupRefPtr, "asan-enable-instantiation-check", true};
232 
233 // If enabled, switches the bucket distribution to a denser one.
234 //
235 // We enable this by default everywhere except for 32-bit Android, since we saw
236 // regressions there.
237 BASE_FEATURE(kPartitionAllocUseDenserDistribution,
238              "PartitionAllocUseDenserDistribution",
239 #if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS)
240              FEATURE_DISABLED_BY_DEFAULT
241 #else
242              FEATURE_ENABLED_BY_DEFAULT
243 #endif  // BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS)
244 );
245 const base::FeatureParam<BucketDistributionMode>::Option
246     kPartitionAllocBucketDistributionOption[] = {
247         {BucketDistributionMode::kDefault, "default"},
248         {BucketDistributionMode::kDenser, "denser"},
249 };
250 const base::FeatureParam<BucketDistributionMode>
251     kPartitionAllocBucketDistributionParam {
252   &kPartitionAllocUseDenserDistribution, "mode",
253 #if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS)
254       BucketDistributionMode::kDefault,
255 #else
256       BucketDistributionMode::kDenser,
257 #endif  // BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS)
258       &kPartitionAllocBucketDistributionOption
259 };
260 
261 BASE_FEATURE(kPartitionAllocMemoryReclaimer,
262              "PartitionAllocMemoryReclaimer",
263              FEATURE_ENABLED_BY_DEFAULT);
264 const base::FeatureParam<TimeDelta> kPartitionAllocMemoryReclaimerInterval = {
265     &kPartitionAllocMemoryReclaimer, "interval",
266     TimeDelta(),  // Defaults to zero.
267 };
268 
269 // Configures whether we set a lower limit for renderers that do not have a main
270 // frame, similar to the limit that is already done for backgrounded renderers.
271 BASE_FEATURE(kLowerPAMemoryLimitForNonMainRenderers,
272              "LowerPAMemoryLimitForNonMainRenderers",
273              FEATURE_DISABLED_BY_DEFAULT);
274 
275 // If enabled, switches PCScan scheduling to a mutator-aware scheduler. Does not
276 // affect whether PCScan is enabled itself.
277 BASE_FEATURE(kPartitionAllocPCScanMUAwareScheduler,
278              "PartitionAllocPCScanMUAwareScheduler",
279              FEATURE_ENABLED_BY_DEFAULT);
280 
281 // If enabled, PCScan frees unconditionally all quarantined objects.
282 // This is a performance testing feature.
283 BASE_FEATURE(kPartitionAllocPCScanImmediateFreeing,
284              "PartitionAllocPCScanImmediateFreeing",
285              FEATURE_DISABLED_BY_DEFAULT);
286 
287 // If enabled, PCScan clears eagerly (synchronously) on free().
288 BASE_FEATURE(kPartitionAllocPCScanEagerClearing,
289              "PartitionAllocPCScanEagerClearing",
290              FEATURE_DISABLED_BY_DEFAULT);
291 
292 // In addition to heap, scan also the stack of the current mutator.
293 BASE_FEATURE(kPartitionAllocPCScanStackScanning,
294              "PartitionAllocPCScanStackScanning",
295 #if BUILDFLAG(PCSCAN_STACK_SUPPORTED)
296              FEATURE_ENABLED_BY_DEFAULT
297 #else
298              FEATURE_DISABLED_BY_DEFAULT
299 #endif  // BUILDFLAG(PCSCAN_STACK_SUPPORTED)
300 );
301 
302 BASE_FEATURE(kPartitionAllocDCScan,
303              "PartitionAllocDCScan",
304              FEATURE_DISABLED_BY_DEFAULT);
305 
306 // Whether to straighten free lists for larger slot spans in PurgeMemory() ->
307 // ... -> PartitionPurgeSlotSpan().
308 BASE_FEATURE(kPartitionAllocStraightenLargerSlotSpanFreeLists,
309              "PartitionAllocStraightenLargerSlotSpanFreeLists",
310              FEATURE_ENABLED_BY_DEFAULT);
311 const base::FeatureParam<
312     partition_alloc::StraightenLargerSlotSpanFreeListsMode>::Option
313     kPartitionAllocStraightenLargerSlotSpanFreeListsModeOption[] = {
314         {partition_alloc::StraightenLargerSlotSpanFreeListsMode::
315              kOnlyWhenUnprovisioning,
316          "only-when-unprovisioning"},
317         {partition_alloc::StraightenLargerSlotSpanFreeListsMode::kAlways,
318          "always"},
319 };
320 const base::FeatureParam<partition_alloc::StraightenLargerSlotSpanFreeListsMode>
321     kPartitionAllocStraightenLargerSlotSpanFreeListsMode = {
322         &kPartitionAllocStraightenLargerSlotSpanFreeLists,
323         "mode",
324         partition_alloc::StraightenLargerSlotSpanFreeListsMode::
325             kOnlyWhenUnprovisioning,
326         &kPartitionAllocStraightenLargerSlotSpanFreeListsModeOption,
327 };
328 
329 // Whether to sort free lists for smaller slot spans in PurgeMemory().
330 BASE_FEATURE(kPartitionAllocSortSmallerSlotSpanFreeLists,
331              "PartitionAllocSortSmallerSlotSpanFreeLists",
332              FEATURE_ENABLED_BY_DEFAULT);
333 
334 // Whether to sort the active slot spans in PurgeMemory().
335 BASE_FEATURE(kPartitionAllocSortActiveSlotSpans,
336              "PartitionAllocSortActiveSlotSpans",
337              FEATURE_DISABLED_BY_DEFAULT);
338 
339 #if BUILDFLAG(IS_WIN)
340 // Whether to retry allocations when commit fails.
341 BASE_FEATURE(kPageAllocatorRetryOnCommitFailure,
342              "PageAllocatorRetryOnCommitFailure",
343              FEATURE_DISABLED_BY_DEFAULT);
344 #endif
345 
346 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
347 // A parameter to exclude or not exclude PartitionAllocSupport from
348 // PartialLowModeOnMidRangeDevices. This is used to see how it affects
349 // renderer performances, e.g. blink_perf.parser benchmark.
350 // The feature: kPartialLowEndModeOnMidRangeDevices is defined in
351 // //base/features.cc. Since the following feature param is related to
352 // PartitionAlloc, define the param here.
353 const FeatureParam<bool> kPartialLowEndModeExcludePartitionAllocSupport{
354     &kPartialLowEndModeOnMidRangeDevices, "exclude-partition-alloc-support",
355     false};
356 #endif
357 
358 BASE_FEATURE(kEnableConfigurableThreadCacheMultiplier,
359              "EnableConfigurableThreadCacheMultiplier",
360              base::FEATURE_DISABLED_BY_DEFAULT);
361 
362 MIRACLE_PARAMETER_FOR_DOUBLE(GetThreadCacheMultiplier,
363                              kEnableConfigurableThreadCacheMultiplier,
364                              "ThreadCacheMultiplier",
365                              2.)
366 
367 MIRACLE_PARAMETER_FOR_DOUBLE(GetThreadCacheMultiplierForAndroid,
368                              kEnableConfigurableThreadCacheMultiplier,
369                              "ThreadCacheMultiplierForAndroid",
370                              1.)
371 
ToPartitionAllocTimeDelta(base::TimeDelta time_delta)372 constexpr partition_alloc::internal::base::TimeDelta ToPartitionAllocTimeDelta(
373     base::TimeDelta time_delta) {
374   return partition_alloc::internal::base::Microseconds(
375       time_delta.InMicroseconds());
376 }
377 
FromPartitionAllocTimeDelta(partition_alloc::internal::base::TimeDelta time_delta)378 constexpr base::TimeDelta FromPartitionAllocTimeDelta(
379     partition_alloc::internal::base::TimeDelta time_delta) {
380   return base::Microseconds(time_delta.InMicroseconds());
381 }
382 
383 BASE_FEATURE(kEnableConfigurableThreadCachePurgeInterval,
384              "EnableConfigurableThreadCachePurgeInterval",
385              base::FEATURE_DISABLED_BY_DEFAULT);
386 
387 MIRACLE_PARAMETER_FOR_TIME_DELTA(
388     GetThreadCacheMinPurgeIntervalValue,
389     kEnableConfigurableThreadCachePurgeInterval,
390     "ThreadCacheMinPurgeInterval",
391     FromPartitionAllocTimeDelta(partition_alloc::kMinPurgeInterval))
392 
393 MIRACLE_PARAMETER_FOR_TIME_DELTA(
394     GetThreadCacheMaxPurgeIntervalValue,
395     kEnableConfigurableThreadCachePurgeInterval,
396     "ThreadCacheMaxPurgeInterval",
397     FromPartitionAllocTimeDelta(partition_alloc::kMaxPurgeInterval))
398 
399 MIRACLE_PARAMETER_FOR_TIME_DELTA(
400     GetThreadCacheDefaultPurgeIntervalValue,
401     kEnableConfigurableThreadCachePurgeInterval,
402     "ThreadCacheDefaultPurgeInterval",
403     FromPartitionAllocTimeDelta(partition_alloc::kDefaultPurgeInterval))
404 
405 const partition_alloc::internal::base::TimeDelta
GetThreadCacheMinPurgeInterval()406 GetThreadCacheMinPurgeInterval() {
407   return ToPartitionAllocTimeDelta(GetThreadCacheMinPurgeIntervalValue());
408 }
409 
410 const partition_alloc::internal::base::TimeDelta
GetThreadCacheMaxPurgeInterval()411 GetThreadCacheMaxPurgeInterval() {
412   return ToPartitionAllocTimeDelta(GetThreadCacheMaxPurgeIntervalValue());
413 }
414 
415 const partition_alloc::internal::base::TimeDelta
GetThreadCacheDefaultPurgeInterval()416 GetThreadCacheDefaultPurgeInterval() {
417   return ToPartitionAllocTimeDelta(GetThreadCacheDefaultPurgeIntervalValue());
418 }
419 
420 BASE_FEATURE(kEnableConfigurableThreadCacheMinCachedMemoryForPurging,
421              "EnableConfigurableThreadCacheMinCachedMemoryForPurging",
422              base::FEATURE_DISABLED_BY_DEFAULT);
423 
424 MIRACLE_PARAMETER_FOR_INT(
425     GetThreadCacheMinCachedMemoryForPurgingBytes,
426     kEnableConfigurableThreadCacheMinCachedMemoryForPurging,
427     "ThreadCacheMinCachedMemoryForPurgingBytes",
428     partition_alloc::kMinCachedMemoryForPurgingBytes)
429 
430 // An apparent quarantine leak in the buffer partition unacceptably
431 // bloats memory when MiraclePtr is enabled in the renderer process.
432 // We believe we have found and patched the leak, but out of an
433 // abundance of caution, we provide this toggle that allows us to
434 // wholly disable MiraclePtr in the buffer partition, if necessary.
435 //
436 // TODO(crbug.com/1444624): this is unneeded once
437 // MiraclePtr-for-Renderer launches.
438 BASE_FEATURE(kPartitionAllocDisableBRPInBufferPartition,
439              "PartitionAllocDisableBRPInBufferPartition",
440              FEATURE_DISABLED_BY_DEFAULT);
441 
442 #if BUILDFLAG(USE_FREELIST_POOL_OFFSETS)
443 BASE_FEATURE(kUsePoolOffsetFreelists,
444              "PartitionAllocUsePoolOffsetFreelists",
445              base::FEATURE_DISABLED_BY_DEFAULT);
446 #endif
447 
448 }  // namespace features
449 }  // namespace base
450