chromium/components/zucchini/exception_filter_helper_win.h

// 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 COMPONENTS_ZUCCHINI_EXCEPTION_FILTER_HELPER_WIN_H_
#define COMPONENTS_ZUCCHINI_EXCEPTION_FILTER_HELPER_WIN_H_

#include <windows.h>

#include <stddef.h>
#include <stdint.h>

#include <map>

#include "base/containers/span.h"

namespace zucchini {

// A helper for handling EXCEPTION_IN_PAGE_ERROR structured exceptions in
// multiple memory ranges; for example, when several files are mapped into the
// process's address space. Consumers must construct an instance outside of
// a `try-except` statement, call `AddRange` one or more times (possibly within
// the `__try` block), and call `FilterPageError` within the filter expression.
// For example:
// ```
//   ExceptionFilterHelper exception_filter_helper;
//   __try {
//     base::MemoryMappedFile mapped_file;
//     if (mapped_file.Initialize("/foo/bar")) {
//       exception_filter_helper.AddRange(mapped_file.data(),
//                                        mapped_file.length());
//       ProcessFile(mapped_file);
//     }
//   } __except(exception_filter_helper.FilterPageError(
//       GetExceptionInformation()->ExceptionRecord) {
//     // I/O error accessing the mapped file.
//   }
// ```
class ExceptionFilterHelper {
 public:
  ExceptionFilterHelper();
  ExceptionFilterHelper(const ExceptionFilterHelper&) = delete;
  ExceptionFilterHelper& operator=(const ExceptionFilterHelper&) = delete;
  ~ExceptionFilterHelper();

  // Adds a memory range within which page errors are to be handled. `range`
  // must not overlap with any previously-added range.
  void AddRange(base::span<const uint8_t> range);

  // Returns `EXCEPTION_EXECUTE_HANDLER` if `exception_record` corresponds to an
  // `EXCEPTION_IN_PAGE_ERROR` for an address wthin a range of memory previously
  // added via `AddRange`; otherwise, returns `EXCEPTION_CONTINUE_SEARCH`.
  int FilterPageError(const EXCEPTION_RECORD* const exception_record);

  // Returns the NTSTATUS of the most-recently handled exception for which
  // `FilterPageError` returned `EXCEPTION_EXECUTE_HANDLER`.
  int32_t nt_status() const { return nt_status_; }

  // Returns `true` if the most-recently handled exception for which
  // `FilterPageError` returned `EXCEPTION_EXECUTE_HANDLER` was caused by a
  // write to a mapped region; otherwise, the exception was caused by a read.
  bool is_write() const { return is_write_; }

 private:
  // Returns `true` if `address` is within any range added to the instance via
  // `AddRange`.
  bool IsInRange(const uint8_t* address) const;

  // A mapping of start address to one-past-end-address for all ranges added to
  // the instance.
  std::map<uintptr_t, uintptr_t> ranges_;

  // The NTSTATUS code of the most-recently handled exception.
  int32_t nt_status_ = 0;

  // True if the most-recently handled exception was caused by a write.
  bool is_write_ = false;
};

}  // namespace zucchini

#endif  // COMPONENTS_ZUCCHINI_EXCEPTION_FILTER_HELPER_WIN_H_