• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 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 "components/prefs/json_pref_store.h"
6 
7 #include <stddef.h>
8 
9 #include <algorithm>
10 #include <string>
11 #include <string_view>
12 #include <utility>
13 
14 #include "base/feature_list.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/functional/bind.h"
18 #include "base/functional/callback.h"
19 #include "base/functional/callback_helpers.h"
20 #include "base/hash/hash.h"
21 #include "base/json/json_file_value_serializer.h"
22 #include "base/json/json_writer.h"
23 #include "base/logging.h"
24 #include "base/memory/ref_counted.h"
25 #include "base/metrics/histogram.h"
26 #include "base/metrics/histogram_macros.h"
27 #include "base/notreached.h"
28 #include "base/observer_list.h"
29 #include "base/ranges/algorithm.h"
30 #include "base/strings/string_number_conversions.h"
31 #include "base/strings/string_util.h"
32 #include "base/task/sequenced_task_runner.h"
33 #include "base/time/default_clock.h"
34 #include "base/values.h"
35 #include "components/prefs/pref_filter.h"
36 
37 // Result returned from internal read tasks.
38 struct JsonPrefStore::ReadResult {
39  public:
40   ReadResult();
41   ~ReadResult();
42   ReadResult(const ReadResult&) = delete;
43   ReadResult& operator=(const ReadResult&) = delete;
44 
45   std::unique_ptr<base::Value> value;
46   PrefReadError error = PersistentPrefStore::PREF_READ_ERROR_NONE;
47   bool no_dir = false;
48   size_t num_bytes_read = 0u;
49 };
50 
51 JsonPrefStore::ReadResult::ReadResult() = default;
52 JsonPrefStore::ReadResult::~ReadResult() = default;
53 
54 namespace {
55 
56 // Some extensions we'll tack on to copies of the Preferences files.
57 const base::FilePath::CharType kBadExtension[] = FILE_PATH_LITERAL("bad");
58 
59 // Report a key that triggers a write into the Preferences files.
ReportKeyChangedToUMA(std::string_view key)60 void ReportKeyChangedToUMA(std::string_view key) {
61   // Truncate the sign bit. Even if the type is unsigned, UMA displays 32-bit
62   // negative numbers.
63   const uint32_t hash = base::PersistentHash(key) & 0x7FFFFFFF;
64   UMA_HISTOGRAM_SPARSE("Prefs.JSonStore.SetValueKey", hash);
65 }
66 
BackupPrefsFile(const base::FilePath & path)67 bool BackupPrefsFile(const base::FilePath& path) {
68   const base::FilePath bad = path.ReplaceExtension(kBadExtension);
69   const bool bad_existed = base::PathExists(bad);
70   base::Move(path, bad);
71   return bad_existed;
72 }
73 
HandleReadErrors(const base::Value * value,const base::FilePath & path,int error_code,const std::string & error_msg)74 PersistentPrefStore::PrefReadError HandleReadErrors(
75     const base::Value* value,
76     const base::FilePath& path,
77     int error_code,
78     const std::string& error_msg) {
79   if (!value) {
80     DVLOG(1) << "Error while loading JSON file: " << error_msg
81              << ", file: " << path.value();
82     switch (error_code) {
83       case JSONFileValueDeserializer::JSON_ACCESS_DENIED:
84         return PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED;
85       case JSONFileValueDeserializer::JSON_CANNOT_READ_FILE:
86         return PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER;
87       case JSONFileValueDeserializer::JSON_FILE_LOCKED:
88         return PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED;
89       case JSONFileValueDeserializer::JSON_NO_SUCH_FILE:
90         return PersistentPrefStore::PREF_READ_ERROR_NO_FILE;
91       default:
92         // JSON errors indicate file corruption of some sort.
93         // Since the file is corrupt, move it to the side and continue with
94         // empty preferences.  This will result in them losing their settings.
95         // We keep the old file for possible support and debugging assistance
96         // as well as to detect if they're seeing these errors repeatedly.
97         // TODO(erikkay) Instead, use the last known good file.
98         // If they've ever had a parse error before, put them in another bucket.
99         // TODO(erikkay) if we keep this error checking for very long, we may
100         // want to differentiate between recent and long ago errors.
101         const bool bad_existed = BackupPrefsFile(path);
102         return bad_existed ? PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT
103                            : PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE;
104     }
105   }
106   if (!value->is_dict())
107     return PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE;
108   return PersistentPrefStore::PREF_READ_ERROR_NONE;
109 }
110 
ReadPrefsFromDisk(const base::FilePath & path)111 std::unique_ptr<JsonPrefStore::ReadResult> ReadPrefsFromDisk(
112     const base::FilePath& path) {
113   int error_code;
114   std::string error_msg;
115   auto read_result = std::make_unique<JsonPrefStore::ReadResult>();
116   JSONFileValueDeserializer deserializer(path);
117   read_result->value = deserializer.Deserialize(&error_code, &error_msg);
118   read_result->error =
119       HandleReadErrors(read_result->value.get(), path, error_code, error_msg);
120   read_result->no_dir = !base::PathExists(path.DirName());
121   read_result->num_bytes_read = deserializer.get_last_read_size();
122 
123   return read_result;
124 }
125 
126 // Returns the a histogram suffix for a few allowlisted JsonPref files.
GetHistogramSuffix(const base::FilePath & path)127 const char* GetHistogramSuffix(const base::FilePath& path) {
128   std::string spaceless_basename;
129   base::ReplaceChars(path.BaseName().MaybeAsASCII(), " ", "_",
130                      &spaceless_basename);
131   // Entries here should be reflected in the ImportantFileClients variant in
132   // histograms.xml.
133   static constexpr std::array<const char*, 4> kAllowList{
134       "Secure_Preferences", "Preferences", "Local_State", "AccountPreferences"};
135   auto it = base::ranges::find(kAllowList, spaceless_basename);
136   return it != kAllowList.end() ? *it : "";
137 }
138 
DoSerialize(base::ValueView value,const base::FilePath & path)139 std::optional<std::string> DoSerialize(base::ValueView value,
140                                        const base::FilePath& path) {
141   std::string output;
142   if (!base::JSONWriter::Write(value, &output)) {
143     // Failed to serialize prefs file. Backup the existing prefs file and
144     // crash.
145     BackupPrefsFile(path);
146     NOTREACHED() << "Failed to serialize preferences : " << path
147                  << "\nBacked up under "
148                  << path.ReplaceExtension(kBadExtension);
149   }
150   return output;
151 }
152 
153 }  // namespace
154 
JsonPrefStore(const base::FilePath & pref_filename,std::unique_ptr<PrefFilter> pref_filter,scoped_refptr<base::SequencedTaskRunner> file_task_runner,bool read_only)155 JsonPrefStore::JsonPrefStore(
156     const base::FilePath& pref_filename,
157     std::unique_ptr<PrefFilter> pref_filter,
158     scoped_refptr<base::SequencedTaskRunner> file_task_runner,
159     bool read_only)
160     : path_(pref_filename),
161       file_task_runner_(std::move(file_task_runner)),
162       read_only_(read_only),
163       writer_(pref_filename,
164               file_task_runner_,
165               GetHistogramSuffix(pref_filename)),
166       pref_filter_(std::move(pref_filter)),
167       initialized_(false),
168       filtering_in_progress_(false),
169       pending_lossy_write_(false),
170       read_error_(PREF_READ_ERROR_NONE),
171       has_pending_write_reply_(false) {
172   DCHECK(!path_.empty());
173 }
174 
GetValue(std::string_view key,const base::Value ** result) const175 bool JsonPrefStore::GetValue(std::string_view key,
176                              const base::Value** result) const {
177   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
178 
179   const base::Value* tmp = prefs_.FindByDottedPath(key);
180   if (!tmp)
181     return false;
182 
183   if (result)
184     *result = tmp;
185   return true;
186 }
187 
GetValues() const188 base::Value::Dict JsonPrefStore::GetValues() const {
189   return prefs_.Clone();
190 }
191 
AddObserver(PrefStore::Observer * observer)192 void JsonPrefStore::AddObserver(PrefStore::Observer* observer) {
193   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
194 
195   observers_.AddObserver(observer);
196 }
197 
RemoveObserver(PrefStore::Observer * observer)198 void JsonPrefStore::RemoveObserver(PrefStore::Observer* observer) {
199   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
200 
201   observers_.RemoveObserver(observer);
202 }
203 
HasObservers() const204 bool JsonPrefStore::HasObservers() const {
205   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
206   return !observers_.empty();
207 }
208 
IsInitializationComplete() const209 bool JsonPrefStore::IsInitializationComplete() const {
210   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
211 
212   return initialized_;
213 }
214 
GetMutableValue(std::string_view key,base::Value ** result)215 bool JsonPrefStore::GetMutableValue(std::string_view key,
216                                     base::Value** result) {
217   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
218 
219   base::Value* tmp = prefs_.FindByDottedPath(key);
220   if (!tmp)
221     return false;
222 
223   if (result)
224     *result = tmp;
225   return true;
226 }
227 
SetValue(std::string_view key,base::Value value,uint32_t flags)228 void JsonPrefStore::SetValue(std::string_view key,
229                              base::Value value,
230                              uint32_t flags) {
231   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
232 
233   base::Value* old_value = prefs_.FindByDottedPath(key);
234   if (!old_value || value != *old_value) {
235     prefs_.SetByDottedPath(key, std::move(value));
236     ReportValueChanged(key, flags);
237     ReportKeyChangedToUMA(key);
238   }
239 }
240 
SetValueSilently(std::string_view key,base::Value value,uint32_t flags)241 void JsonPrefStore::SetValueSilently(std::string_view key,
242                                      base::Value value,
243                                      uint32_t flags) {
244   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
245 
246   base::Value* old_value = prefs_.FindByDottedPath(key);
247   if (!old_value || value != *old_value) {
248     prefs_.SetByDottedPath(key, std::move(value));
249     ScheduleWrite(flags);
250     ReportKeyChangedToUMA(key);
251   }
252 }
253 
RemoveValue(std::string_view key,uint32_t flags)254 void JsonPrefStore::RemoveValue(std::string_view key, uint32_t flags) {
255   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
256 
257   if (prefs_.RemoveByDottedPath(key)) {
258     ReportValueChanged(key, flags);
259   }
260 }
261 
RemoveValueSilently(std::string_view key,uint32_t flags)262 void JsonPrefStore::RemoveValueSilently(std::string_view key, uint32_t flags) {
263   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
264 
265   prefs_.RemoveByDottedPath(key);
266   ScheduleWrite(flags);
267 }
268 
RemoveValuesByPrefixSilently(std::string_view prefix)269 void JsonPrefStore::RemoveValuesByPrefixSilently(std::string_view prefix) {
270   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
271   RemoveValueSilently(prefix, /*flags*/ 0);
272 }
273 
ReadOnly() const274 bool JsonPrefStore::ReadOnly() const {
275   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
276 
277   return read_only_;
278 }
279 
GetReadError() const280 PersistentPrefStore::PrefReadError JsonPrefStore::GetReadError() const {
281   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
282 
283   return read_error_;
284 }
285 
ReadPrefs()286 PersistentPrefStore::PrefReadError JsonPrefStore::ReadPrefs() {
287   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
288 
289   OnFileRead(ReadPrefsFromDisk(path_));
290   return filtering_in_progress_ ? PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE
291                                 : read_error_;
292 }
293 
ReadPrefsAsync(ReadErrorDelegate * error_delegate)294 void JsonPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
295   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
296 
297   initialized_ = false;
298   error_delegate_.emplace(error_delegate);
299 
300   // Weakly binds the read task so that it doesn't kick in during shutdown.
301   file_task_runner_->PostTaskAndReplyWithResult(
302       FROM_HERE, base::BindOnce(&ReadPrefsFromDisk, path_),
303       base::BindOnce(&JsonPrefStore::OnFileRead,
304                      weak_ptr_factory_.GetWeakPtr()));
305 }
306 
CommitPendingWrite(base::OnceClosure reply_callback,base::OnceClosure synchronous_done_callback)307 void JsonPrefStore::CommitPendingWrite(
308     base::OnceClosure reply_callback,
309     base::OnceClosure synchronous_done_callback) {
310   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
311 
312   // Schedule a write for any lossy writes that are outstanding to ensure that
313   // they get flushed when this function is called.
314   SchedulePendingLossyWrites();
315 
316   if (writer_.HasPendingWrite() && !read_only_)
317     writer_.DoScheduledWrite();
318 
319   // Since disk operations occur on |file_task_runner_|, the reply of a task
320   // posted to |file_task_runner_| will run after currently pending disk
321   // operations. Also, by definition of PostTaskAndReply(), the reply (in the
322   // |reply_callback| case will run on the current sequence.
323 
324   if (synchronous_done_callback) {
325     file_task_runner_->PostTask(FROM_HERE,
326                                 std::move(synchronous_done_callback));
327   }
328 
329   if (reply_callback) {
330     file_task_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
331                                         std::move(reply_callback));
332   }
333 }
334 
SchedulePendingLossyWrites()335 void JsonPrefStore::SchedulePendingLossyWrites() {
336   if (pending_lossy_write_)
337     writer_.ScheduleWrite(this);
338 }
339 
ReportValueChanged(std::string_view key,uint32_t flags)340 void JsonPrefStore::ReportValueChanged(std::string_view key, uint32_t flags) {
341   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
342 
343   if (pref_filter_)
344     pref_filter_->FilterUpdate(key);
345 
346   for (PrefStore::Observer& observer : observers_)
347     observer.OnPrefValueChanged(key);
348 
349   ScheduleWrite(flags);
350 }
351 
PerformPreserializationTasks()352 void JsonPrefStore::PerformPreserializationTasks() {
353   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
354   pending_lossy_write_ = false;
355   if (pref_filter_) {
356     OnWriteCallbackPair callbacks = pref_filter_->FilterSerializeData(prefs_);
357     if (!callbacks.first.is_null() || !callbacks.second.is_null())
358       RegisterOnNextWriteSynchronousCallbacks(std::move(callbacks));
359   }
360 }
361 
RunOrScheduleNextSuccessfulWriteCallback(bool write_success)362 void JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback(
363     bool write_success) {
364   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
365 
366   has_pending_write_reply_ = false;
367   if (!on_next_successful_write_reply_.is_null()) {
368     base::OnceClosure on_successful_write =
369         std::move(on_next_successful_write_reply_);
370     if (write_success) {
371       std::move(on_successful_write).Run();
372     } else {
373       RegisterOnNextSuccessfulWriteReply(std::move(on_successful_write));
374     }
375   }
376 }
377 
378 // static
PostWriteCallback(base::OnceCallback<void (bool success)> on_next_write_callback,base::OnceCallback<void (bool success)> on_next_write_reply,scoped_refptr<base::SequencedTaskRunner> reply_task_runner,bool write_success)379 void JsonPrefStore::PostWriteCallback(
380     base::OnceCallback<void(bool success)> on_next_write_callback,
381     base::OnceCallback<void(bool success)> on_next_write_reply,
382     scoped_refptr<base::SequencedTaskRunner> reply_task_runner,
383     bool write_success) {
384   if (!on_next_write_callback.is_null())
385     std::move(on_next_write_callback).Run(write_success);
386 
387   // We can't run |on_next_write_reply| on the current thread. Bounce back to
388   // the |reply_task_runner| which is the correct sequenced thread.
389   reply_task_runner->PostTask(
390       FROM_HERE, base::BindOnce(std::move(on_next_write_reply), write_success));
391 }
392 
RegisterOnNextSuccessfulWriteReply(base::OnceClosure on_next_successful_write_reply)393 void JsonPrefStore::RegisterOnNextSuccessfulWriteReply(
394     base::OnceClosure on_next_successful_write_reply) {
395   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
396   DCHECK(on_next_successful_write_reply_.is_null());
397 
398   on_next_successful_write_reply_ = std::move(on_next_successful_write_reply);
399 
400   // If there are pending callbacks, avoid erasing them; the reply will be used
401   // as we set |on_next_successful_write_reply_|. Otherwise, setup a reply with
402   // an empty callback.
403   if (!has_pending_write_reply_) {
404     has_pending_write_reply_ = true;
405     writer_.RegisterOnNextWriteCallbacks(
406         base::OnceClosure(),
407         base::BindOnce(
408             &PostWriteCallback, base::OnceCallback<void(bool success)>(),
409             base::BindOnce(
410                 &JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback,
411                 weak_ptr_factory_.GetWeakPtr()),
412             base::SequencedTaskRunner::GetCurrentDefault()));
413   }
414 }
415 
RegisterOnNextWriteSynchronousCallbacks(OnWriteCallbackPair callbacks)416 void JsonPrefStore::RegisterOnNextWriteSynchronousCallbacks(
417     OnWriteCallbackPair callbacks) {
418   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
419 
420   has_pending_write_reply_ = true;
421 
422   writer_.RegisterOnNextWriteCallbacks(
423       std::move(callbacks.first),
424       base::BindOnce(
425           &PostWriteCallback, std::move(callbacks.second),
426           base::BindOnce(
427               &JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback,
428               weak_ptr_factory_.GetWeakPtr()),
429           base::SequencedTaskRunner::GetCurrentDefault()));
430 }
431 
OnStoreDeletionFromDisk()432 void JsonPrefStore::OnStoreDeletionFromDisk() {
433   if (pref_filter_)
434     pref_filter_->OnStoreDeletionFromDisk();
435 }
436 
OnFileRead(std::unique_ptr<ReadResult> read_result)437 void JsonPrefStore::OnFileRead(std::unique_ptr<ReadResult> read_result) {
438   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
439 
440   DCHECK(read_result);
441 
442   base::Value::Dict unfiltered_prefs;
443 
444   read_error_ = read_result->error;
445 
446   bool initialization_successful = !read_result->no_dir;
447 
448   if (initialization_successful) {
449     switch (read_error_) {
450       case PREF_READ_ERROR_ACCESS_DENIED:
451       case PREF_READ_ERROR_FILE_OTHER:
452       case PREF_READ_ERROR_FILE_LOCKED:
453       case PREF_READ_ERROR_JSON_TYPE:
454       case PREF_READ_ERROR_FILE_NOT_SPECIFIED:
455         read_only_ = true;
456         break;
457       case PREF_READ_ERROR_NONE:
458         DCHECK(read_result->value);
459         DCHECK(read_result->value->is_dict());
460         writer_.set_previous_data_size(read_result->num_bytes_read);
461         unfiltered_prefs = std::move(*read_result->value).TakeDict();
462         break;
463       case PREF_READ_ERROR_NO_FILE:
464         // If the file just doesn't exist, maybe this is first run.  In any case
465         // there's no harm in writing out default prefs in this case.
466       case PREF_READ_ERROR_JSON_PARSE:
467       case PREF_READ_ERROR_JSON_REPEAT:
468         break;
469       case PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
470         // This is a special error code to be returned by ReadPrefs when it
471         // can't complete synchronously, it should never be returned by the read
472         // operation itself.
473       case PREF_READ_ERROR_MAX_ENUM:
474         NOTREACHED();
475     }
476   }
477 
478   if (pref_filter_) {
479     filtering_in_progress_ = true;
480     PrefFilter::PostFilterOnLoadCallback post_filter_on_load_callback(
481         base::BindOnce(&JsonPrefStore::FinalizeFileRead,
482                        weak_ptr_factory_.GetWeakPtr(),
483                        initialization_successful));
484     pref_filter_->FilterOnLoad(std::move(post_filter_on_load_callback),
485                                std::move(unfiltered_prefs));
486   } else {
487     FinalizeFileRead(initialization_successful, std::move(unfiltered_prefs),
488                      false);
489   }
490 }
491 
~JsonPrefStore()492 JsonPrefStore::~JsonPrefStore() {
493   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
494   CommitPendingWrite();
495 }
496 
SerializeData()497 std::optional<std::string> JsonPrefStore::SerializeData() {
498   PerformPreserializationTasks();
499   return DoSerialize(prefs_, path_);
500 }
501 
502 base::ImportantFileWriter::BackgroundDataProducerCallback
GetSerializedDataProducerForBackgroundSequence()503 JsonPrefStore::GetSerializedDataProducerForBackgroundSequence() {
504   PerformPreserializationTasks();
505   return base::BindOnce(&DoSerialize, prefs_.Clone(), path_);
506 }
507 
FinalizeFileRead(bool initialization_successful,base::Value::Dict prefs,bool schedule_write)508 void JsonPrefStore::FinalizeFileRead(bool initialization_successful,
509                                      base::Value::Dict prefs,
510                                      bool schedule_write) {
511   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
512 
513   filtering_in_progress_ = false;
514 
515   if (!initialization_successful) {
516     for (PrefStore::Observer& observer : observers_)
517       observer.OnInitializationCompleted(false);
518     return;
519   }
520 
521   prefs_ = std::move(prefs);
522 
523   initialized_ = true;
524 
525   if (schedule_write)
526     ScheduleWrite(DEFAULT_PREF_WRITE_FLAGS);
527 
528   if (error_delegate_.has_value() && error_delegate_.value() &&
529       read_error_ != PREF_READ_ERROR_NONE) {
530     error_delegate_.value()->OnError(read_error_);
531   }
532 
533   for (PrefStore::Observer& observer : observers_)
534     observer.OnInitializationCompleted(true);
535 
536   return;
537 }
538 
ScheduleWrite(uint32_t flags)539 void JsonPrefStore::ScheduleWrite(uint32_t flags) {
540   if (read_only_)
541     return;
542 
543   if (flags & LOSSY_PREF_WRITE_FLAG) {
544     pending_lossy_write_ = true;
545   } else {
546     writer_.ScheduleWriteWithBackgroundDataSerializer(this);
547   }
548 }
549 
HasReadErrorDelegate() const550 bool JsonPrefStore::HasReadErrorDelegate() const {
551   return error_delegate_.has_value();
552 }
553