/*
 * Copyright (c) 2024 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

/*
 * vm_configurator.h
 *
 * Generator and checker of userspace virtual memory configurations.
 */

#ifndef VM_CONFIGURATOR_H
#define VM_CONFIGURATOR_H

/*
 * -- Dramatis personae --
 *
 * vm_entry_template_t
 *     Specification of a VM entry to create,
 *     or a hole in VM address space to skip over.
 *     Used to describe and create the VM state for the start of a test.
 *
 * vm_object_template_t
 *     Specification of a VM object to create for entries to copy or share.
 *     Used to describe and create the VM state for the start of a test.
 *
 * vm_config_t
 *     Specification of one or more contiguous VM entries,
 *     plus a test name and an address range within that VM
 *     space that is the range to be tested.
 *     Used to describe and create the VM state for the start of a test.
 *
 * vm_entry_checker_t
 *     Describes the expected state of a VM entry or a hole,
 *     and verifies that the live VM state matches the expected state.
 *     Updated by test code as test operations are performed.
 *     Used to verify the VM state during and after a test.
 *
 * vm_object_checker_t
 *     Describes the expected state of a VM object
 *     and verifies that the live VM state matches the expected state
 *     Updated by test code as test operations are performed.
 *     Used to verify the VM state during and after a test.
 *
 * -- Outline of a test --
 *
 * 1. Describe the desired initial memory state
 *    with arrays of vm_entry_template_t and vm_object_template_t.
 * 2. Call create_vm_state() to allocate the specified VM entries
 *    and lists of vm_entry_checker_t and vm_object_checker_t
 *    that match the newly-allocated state.
 * 3. Perform the VM operations to be tested. Update the checkers
 *    with the state changes that you expect. If some field's value
 *    becomes indeterminate, or difficult to specify and unimportant
 *    for your test, disable that field in the checker.
 * 4. Call verify_vm_state() to compare the live
 *    VM state to the checker's expected state.
 * 5. Optionally repeat steps 3 and 4 to test a sequence of VM operations.
 *
 * See vm_configurator_tests.h for a set of templates used by
 * many VM syscall tests, and some details on how to run them.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <mach/vm_prot.h>
#include <mach/vm_param.h>
#include <mach/vm_region.h>
#include <mach/vm_inherit.h>
#include <mach/vm_behavior.h>
#include <mach/vm_statistics.h>

#include <darwintest.h>
#include <darwintest_utils.h>
#include <test_utils.h>

/*
 * Set Verbose = true to log the complete VM state, both expected and actual,
 * every time it is checked.
 * Initialized from environment variable VERBOSE
 */
extern bool Verbose;

/*
 * Return values from individual test functions.
 * These are ordered from "best" to "worst".
 *
 * TODO: docs
 */
typedef enum {
	TestSucceeded = 1,
	TestFailed,
} test_result_t;

static inline test_result_t
worst_result(test_result_t *list, unsigned count)
{
	test_result_t worst = TestSucceeded;
	for (unsigned i = 0; i < count; i++) {
		if (list[i] > worst) {
			worst = list[i];
		}
	}
	return worst;
}

typedef enum {
	DontFill = 0, /* must be zero */
	Fill = 1
} fill_pattern_mode_t;

typedef struct {
	fill_pattern_mode_t mode;
	uint64_t pattern;
} fill_pattern_t;

/*
 * EndObjects: for END_OBJECTS array terminator
 * Deinited: an object that is no longer referenced and whose checker is now
 *   depopulated but is still allocated because some checker list may point to it
 * Anonymous: anonymous memory such as vm_allocate()
 * SubmapObject: an "object" that is really a submap
 * TODO: support named/pageable objects
 */
typedef enum {
	FreedObject = 0,  /* use after free, shouldn't happen */
	EndObjects,
	Deinited,
	Anonymous,
	SubmapObject,
} vm_object_template_kind_t;

/*
 * struct vm_object_template_t
 * Declaratively specify VM objects to be created.
 */
typedef struct vm_object_template_s {
	vm_object_template_kind_t kind;

	mach_vm_size_t size;  /* size 0 means auto-compute from entry sizes */

	fill_pattern_t fill_pattern;
	struct {
		struct vm_entry_template_s *entries;
		struct vm_object_template_s *objects;
		unsigned entry_count;
		unsigned object_count;
	} submap;
} vm_object_template_t;

/*
 * Convenience macro for initializing a vm_object_template_t.
 * The macro sets all template fields to a default value.
 * You may override any field using designated initializer syntax.
 *
 * Example usage:
 *     // all default values
 *     vm_object_template()
 *
 *     // default, with custom size and fill pattern
 *     vm_object_template(
 *             .size = 20 * PAGE_SIZE,
 *             .fill_pattern = 0x1234567890abcdef)
 */
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#define vm_object_template(...)                                         \
	_Pragma("clang diagnostic push")                                \
	_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
	_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
	(vm_object_template_t){                                         \
	        .size = 0, /* auto-computed */                          \
	            .kind = Anonymous,                                  \
	            .fill_pattern = {.mode = DontFill},                 \
	            __VA_ARGS__                                         \
	            }                                                   \
	_Pragma("clang diagnostic pop")

/* Convenience for submap objects */
#define submap_object_template(...) \
	vm_object_template(.kind = SubmapObject, __VA_ARGS__)

/*
 * EndEntries: for END_ENTRIES array terminator
 * Allocation: an ordinary VM entry
 * Hole: an unallocated range of the address space.
 * Submap: a mapping of a submap
 */
typedef enum {
	EndEntries = 0,
	Allocation,
	Hole,
	Submap,
} vm_entry_template_kind_t;

/*
 * struct vm_entry_template_t
 * Declaratively specify VM entries to be created.
 */
typedef struct vm_entry_template_s {
	mach_vm_size_t size;
	vm_entry_template_kind_t kind;

	/*
	 * NULL object means either null vm_object_t or anonymous zerofilled
	 * memory, depending on the requirements of the other settings.
	 * (For example, non-zero wire count faults in the pages
	 * so it is no longer a null vm_object_t.)
	 * Used when .kind == Allocation.
	 */
	vm_object_template_t *object;

	mach_vm_offset_t offset;

	vm_prot_t protection;
	vm_prot_t max_protection;
	vm_inherit_t inheritance;
	vm_behavior_t behavior;
	bool permanent;

	/* New entry gets vm_wire'd this many times. */
	uint16_t user_wired_count;

	/*
	 * User tag may be a specific value, or autoincrementing.
	 *
	 * An autoincrementing tag is assigned by create_vm_state()
	 * in the VM_MEMORY_APPLICATION_SPECIFIC_1-16 range. Adjacent
	 * autoincrementing entries get distinct tags. This can be
	 * used to stop the VM from simplifying/coalescing vm entries
	 * that you want to remain separate.
	 */
	uint16_t user_tag;
#define VM_MEMORY_TAG_AUTOINCREMENTING 256

	uint8_t share_mode;

	/*
	 * Code to update when adding new fields:
	 *     vm_entry_template() macro
	 *     create_vm_state() function
	 */
} vm_entry_template_t;


/*
 * Default size for vm_entries created by this generator
 * Some tests require that this be above some minimum.
 * 64 * PAGE_SIZE is big enough that 1/4 of an entry is
 * still over the 32KB physical copy limit inside vm_map_copyin.
 */
#define DEFAULT_ENTRY_SIZE (64 * (mach_vm_address_t)PAGE_SIZE)

/*
 * Default size for address ranges that cover only part of a vm_entry.
 * Some tests require that this be above some minimum.
 */
#define DEFAULT_PARTIAL_ENTRY_SIZE (DEFAULT_ENTRY_SIZE / 2u)

/*
 * Unnesting of submap nested pmaps occurs at L[N-1] page table
 * boundaries (pmap "twig"). By default we avoid crossing those
 * boundaries in tests because it affects the unnested map entries
 * in the parent map.
 * TODO: don't hardcode this, get it from pmap somehow
 */
#define SUBMAP_ALIGNMENT_MASK (0x2000000ull - 1)

/*
 * Convenience macro for initializing a vm_entry_template_t.
 * The macro sets all template fields to a default value.
 * You may override any field using designated initializer syntax.
 *
 * Example usage:
 *     // all default values
 *     vm_entry_template()
 *
 *     // default, with custom size and protections
 *     vm_entry_template(
 *             .size = 20 * PAGE_SIZE,
 *             .protection = VM_PROT_READ,
 *             .max_protection = VM_PROT_READ | VM_PROT_WRITE)
 */
#define vm_entry_template(...)                                          \
	_Pragma("clang diagnostic push")                                \
	_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
	_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
	(vm_entry_template_t){                                          \
	        .size = DEFAULT_ENTRY_SIZE,                             \
	            .kind = Allocation,                                 \
	            .object = NULL,                                     \
	            .offset = 0,                                        \
	            .protection = VM_PROT_READ | VM_PROT_WRITE,         \
	            .max_protection = VM_PROT_READ | VM_PROT_WRITE,     \
	            .inheritance = VM_INHERIT_DEFAULT, /* inherit_copy */ \
	            .behavior = VM_BEHAVIOR_DEFAULT,                    \
	            .permanent = false,                                 \
	            .user_wired_count = 0,                              \
	            .user_tag = VM_MEMORY_TAG_AUTOINCREMENTING,         \
	            .share_mode = SM_EMPTY,                             \
	            __VA_ARGS__                                         \
	            }                                                   \
	_Pragma("clang diagnostic pop")

/* Convenience for submap entries */
#define submap_entry_template(...)              \
	vm_entry_template(.kind = Submap, __VA_ARGS__)

/*
 * Convenience templates.
 * END_ENTRIES and END_OBJECTS: terminates a template list
 *     passed to create_vm_state() instead of passing an array count.
 *     (useful for hand-written template array initializers)
 * guard_entry_template: an allocation that defaults to
 *     prot/max NONE/NONE and tag VM_MEMORY_GUARD
 * hole_template: an unallocated hole in the address space.
 */
extern vm_object_template_t END_OBJECTS;
extern vm_entry_template_t END_ENTRIES;
extern vm_entry_template_t guard_entry_template;
extern vm_entry_template_t hole_template;

/*
 * Count the number of templates in an END_TEMPLATE-terminated array.
 */
extern unsigned
count_templates(const vm_entry_template_t *templates);


/*
 * struct vm_entry_attribute_list_t
 * A list of checkable entry attributes with one bool for each.
 * Used to record which attributes should be verified by a checker,
 * or which attributes failed to match during verification.
 */
typedef struct {
	union {
		uint64_t bits;
		struct {
			uint64_t address_attr:1;
			uint64_t size_attr:1;
			uint64_t object_attr:1;
			uint64_t protection_attr:1;
			uint64_t max_protection_attr:1;
			uint64_t inheritance_attr:1;
			uint64_t behavior_attr:1;
			uint64_t permanent_attr:1;
			uint64_t user_wired_count_attr:1;
			uint64_t user_tag_attr:1;
			uint64_t is_submap_attr:1;
			uint64_t submap_depth_attr:1;
			uint64_t object_offset_attr:1;
			uint64_t pages_resident_attr:1;
			uint64_t share_mode_attr:1;
		};
	};

	/*
	 * Code to update when adding new fields:
	 * dump_checker_info()
	 * vm_entry_attributes_with_default macro
	 * verify_allocation()
	 */
} vm_entry_attribute_list_t;

/*
 * struct vm_object_attribute_list_t
 * A list of checkable entry attributes with one bool for each.
 * Used to record which attributes should be verified by a checker,
 * or which attributes failed to match during verification.
 */
typedef struct {
	union {
		uint64_t bits;
		struct {
			uint64_t object_id_attr:1;
			uint64_t size_attr:1;
			uint64_t ref_count_attr:1;
			uint64_t shadow_depth_attr:1;
			uint64_t fill_pattern_attr:1;
		};
	};

	/*
	 * Code to update when adding new fields:
	 * dump_checker_info()
	 * vm_object_attributes_with_default macro
	 * verify_allocation()
	 */
} vm_object_attribute_list_t;

/*
 * vm_entry_attributes_with_default() returns a vm_entry_attribute_list_t,
 * with all attributes set to `default_value`, and the caller can set individual
 * attributes to other values using designated initializer syntax.
 */
#define vm_entry_attributes_with_default(default_value, ...)            \
	_Pragma("clang diagnostic push")                                \
	_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
	_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
	(vm_entry_attribute_list_t){                                    \
	        .address_attr           = (default_value),              \
	        .size_attr              = (default_value),              \
	        .object_attr            = (default_value),              \
	        .protection_attr        = (default_value),              \
	        .max_protection_attr    = (default_value),              \
	        .inheritance_attr       = (default_value),              \
	        .behavior_attr          = (default_value),              \
	        .permanent_attr         = (default_value),              \
	        .user_wired_count_attr  = (default_value),              \
	        .user_tag_attr          = (default_value),              \
	        .is_submap_attr         = (default_value),              \
	        .submap_depth_attr      = (default_value),              \
	        .object_offset_attr     = (default_value),              \
	        .pages_resident_attr    = (default_value),              \
	        .share_mode_attr        = (default_value),              \
	        __VA_ARGS__                                             \
	    }                                                           \
	_Pragma("clang diagnostic pop")

/*
 * vm_object_attributes_with_default() returns a vm_object_attribute_list_t,
 * with all attributes set to `default_value`, and the caller can set individual
 * attributes to other values using designated initializer syntax.
 */
#define vm_object_attributes_with_default(default_value, ...)           \
	_Pragma("clang diagnostic push")                                \
	_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
	_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
	(vm_object_attribute_list_t){                                   \
	        .object_id_attr    = (default_value),                   \
	        .size_attr         = (default_value),                   \
	        .ref_count_attr    = (default_value),                   \
	        .shadow_depth_attr = (default_value),                   \
	        .fill_pattern_attr = (default_value),                   \
	        __VA_ARGS__                                             \
	    }                                                           \
	_Pragma("clang diagnostic pop")

/*
 * Description of a checker's current knowledge of an object's ID.
 * object_is_unknown: object'd ID is unknown; it may be null
 * object_has_unknown_nonnull_id: object's ID is expected to be non-null,
 *     but its actual value is unknown
 * object_has_known_id: object's ID is expected to be checker->object_id
 *
 * During verification unknown object IDs are learned by reading them from the
 * actual VM state. The learned IDs are applied to subsequent verifications or
 * to subsequent uses of the same object in the same verification.
 */
typedef enum {
	object_is_unknown = 0,
	object_has_unknown_nonnull_id,
	object_has_known_id
} object_id_mode_t;

/*
 * struct vm_object_checker_t
 * Maintain and verify expected state of a VM object.
 */
typedef struct vm_object_checker_s {
	struct vm_object_checker_s *prev;
	struct vm_object_checker_s *next;

	vm_object_template_kind_t kind;
	vm_object_attribute_list_t verify;
	bool deinited;

	uint64_t object_id;
	object_id_mode_t object_id_mode;

	/*
	 * This is the count of references to this object specifically.
	 * vm_region's reported ref_count also includes references to
	 * the shadow chain's objects, minus the shadow chain's references
	 * to each other.
	 */
	unsigned self_ref_count;
	mach_vm_size_t size;
	fill_pattern_t fill_pattern;

	/*
	 * Shadow chain.
	 * object->shadow moves away from entry.
	 * object->shadow is refcounted.
	 */
	struct vm_object_checker_s *shadow;

	/*
	 * Checkers for submap contents.
	 * These checkers are configured for a mapping of the whole
	 * submap at address 0. Verification of actual remappings will
	 * need to compensate for address offsets and bounds clipping.
	 */
	struct checker_list_s *submap_checkers;

	/*
	 * Code to update when adding new fields:
	 *     struct vm_object_attribute_list_t
	 *     make_null_object_checker()
	 *     make_anonymous_object_checker()
	 *     make_submap_object_checker()
	 *     dump_checker_info()
	 *     verify_allocation()
	 *     object_checker_clone()
	 */
} vm_object_checker_t;

/*
 * Create a new object checker duplicating an existing checker.
 * The new object is:
 * - zero self_ref_count
 * - unknown object_id
 * - not linked into any checker_list
 */
extern vm_object_checker_t *
object_checker_clone(vm_object_checker_t *obj_checker);

/*
 * struct vm_entry_checker_t
 * Maintain and verify expected state of a VM map entry.
 *
 * The `verify` bitmap specifies which properties should be checked.
 * If a property's value is indeterminate, or is difficult to specify
 * and not important to the test, that check can be disabled.
 *
 * Checkers are kept in a doubly-linked list in address order,
 * similar to vm_map_entry_t but it is not a circular list.
 * Submaps are recursive: the top-level list contains a Submap checker,
 *   and the Submap checker has its own list of contained checkers.
 */
typedef struct vm_entry_checker_s {
	struct vm_entry_checker_s *prev;
	struct vm_entry_checker_s *next;

	vm_entry_template_kind_t kind;
	vm_entry_attribute_list_t verify;

	mach_vm_address_t address;
	mach_vm_size_t size;

	vm_object_checker_t *object;

	vm_prot_t protection;
	vm_prot_t max_protection;
	vm_inherit_t inheritance;
	vm_behavior_t behavior;
	bool permanent;

	uint16_t user_wired_count;
	uint8_t user_tag;

	bool is_submap;         /* true when entry is a parent map's submap entry */
	uint32_t submap_depth;  /* non-zero when entry is a submap's content */

	uint64_t object_offset;
	uint32_t pages_resident;  /* TODO: track this in the object checker instead */

	bool needs_copy;

	/* share_mode is computed from other entry and object attributes */

	/*
	 * Code to update when adding new fields:
	 *     struct vm_entry_attribute_list_t
	 *     make_checker_for_anonymous_private()
	 *     make_checker_for_vm_allocate()
	 *     make_checker_for_shared()
	 *     make_checker_for_submap()
	 *     dump_checker_info()
	 *     verify_allocation()
	 *     checker_simplify_left()
	 */
} vm_entry_checker_t;

/*
 * A list of consecutive entry checkers. May be a subset of the entire doubly-linked list.
 */
typedef struct {
	vm_entry_checker_t *head;
	vm_entry_checker_t *tail;
} entry_checker_range_t;

/*
 * Count the number of entries between
 * checker_range->head and checker_range->tail, inclusive.
 */
extern unsigned
checker_range_count(entry_checker_range_t checker_range);

/*
 * Return the start address of the first entry in a range.
 */
extern mach_vm_address_t
checker_range_start_address(entry_checker_range_t checker_range);

/*
 * Return the end address of the last entry in a range.
 */
extern mach_vm_address_t
checker_range_end_address(entry_checker_range_t checker_range);

/*
 * Return size of all entries in a range.
 */
extern mach_vm_size_t
checker_range_size(entry_checker_range_t checker_range);

/*
 * Loop over all checkers between
 * entry_range->head and entry_range->tail, inclusive.
 * Does visit any submap parent entry.
 * Does not descend into submap contents.
 *
 * You may clip_left the current checker. The new left entry is not visited.
 * You may clip_right the current checker. The new right entry is visited next.
 * You may not delete the current checker, unless you also immediately break the loop.
 */
#define FOREACH_CHECKER(checker, entry_range)                   \
	for (vm_entry_checker_t *checker = (entry_range).head;  \
	     checker != (entry_range).tail->next;               \
	     checker = checker->next)

/*
 * The list of all entry and object checkers.
 * The first and last entries may be changed by the test.
 * The first object is the common null object, so it should not change.
 *
 * Submaps get their own checker_list_t. A submap checker
 * list stores checkers for the submap's map entries.
 * It does not store any objects; a single global list of objects is
 * maintained in the top-level checker list so it can be searched by ID.
 *
 * submap_slide keeps track of a temporary address offset applied
 * to the contained checkers. This is used for submap contents.
 */
typedef struct checker_list_s {
	struct checker_list_s *parent;
	entry_checker_range_t entries;
	vm_object_checker_t *objects; /* must be NULL in submaps */
	uint64_t submap_slide;
	bool is_slid;
} checker_list_t;

#define FOREACH_OBJECT_CHECKER(obj_checker, list) \
	for (vm_object_checker_t *obj_checker = (list)->objects; \
	     obj_checker != NULL; \
	     obj_checker = obj_checker->next)

/*
 * Return the nth checker in the list. Aborts if n is out of range.
 */
extern vm_entry_checker_t *
checker_list_nth(checker_list_t *list, unsigned n);

/*
 * Search a list of checkers for an allocation that contains the given address.
 * Returns NULL if no checker contains the address.
 * Returns NULL if a non-Allocation checker contains the address.
 * Does not descend into submaps.
 */
extern vm_entry_checker_t *
checker_list_find_allocation(checker_list_t *list, mach_vm_address_t addr);

/*
 * Search a list of checkers for a checker that contains the given address.
 * May return checkers for holes.
 * Returns NULL if no checker contains the address.
 * Does not descend into submaps.
 */
extern vm_entry_checker_t *
checker_list_find_checker(checker_list_t *list, mach_vm_address_t addr);

/*
 * Add a new vm object checker to the list.
 * Aborts if the new object is null and the list already has its null object.
 * Aborts if the object's ID is the same as some other object.
 */
extern void
checker_list_append_object(
	checker_list_t *list,
	vm_object_checker_t *obj_checker);

/*
 * Return the list of entry checkers covering an address range.
 * Aborts if the range includes any hole checkers.
 */
extern entry_checker_range_t
checker_list_find_range(
	checker_list_t *list,
	mach_vm_address_t start,
	mach_vm_size_t size);

/*
 * Return the list of entry checkers covering an address range.
 * Hole checkers are allowed.
 */
extern entry_checker_range_t
checker_list_find_range_including_holes(
	checker_list_t *list,
	mach_vm_address_t start,
	mach_vm_size_t size);

/*
 * Like checker_list_find_range(),
 * but the first and last entries are clipped to the address range.
 */
extern entry_checker_range_t
checker_list_find_and_clip(
	checker_list_t *list,
	mach_vm_address_t start,
	mach_vm_size_t size);

/*
 * Like checker_list_find_range_including_holes(),
 * but the first and last entries (if any) are clipped to the address range.
 */
extern entry_checker_range_t
checker_list_find_and_clip_including_holes(
	checker_list_t *list,
	mach_vm_address_t start,
	mach_vm_size_t size);

/*
 * Attempts to simplify all entries in an address range.
 */
extern void
checker_list_simplify(
	checker_list_t *list,
	mach_vm_address_t start,
	mach_vm_size_t size);

/*
 * Replace and delete checkers in old_range
 * with the checkers in new_range.
 * The two ranges must have the same start address and size.
 * Updates list->head and/or list->tail if necessary.
 */
extern void
checker_list_replace_range(
	checker_list_t *list,
	entry_checker_range_t old_range,
	entry_checker_range_t new_range);

/*
 * Convenience function to replace one checker with another.
 * The two checkers must have the same start address and size.
 */
static inline void
checker_list_replace_checker(
	checker_list_t *list,
	vm_entry_checker_t *old_checker,
	vm_entry_checker_t *new_checker)
{
	checker_list_replace_range(list,
	    (entry_checker_range_t){ old_checker, old_checker },
	    (entry_checker_range_t){ new_checker, new_checker });
}

/*
 * Convenience function to replace one checker with several checkers.
 * The old and the new must have the same start address and size.
 */
static inline void
checker_list_replace_checker_with_range(
	checker_list_t *list,
	vm_entry_checker_t *old_checker,
	entry_checker_range_t new_checkers)
{
	checker_list_replace_range(list,
	    (entry_checker_range_t){ old_checker, old_checker },
	    new_checkers);
}

/*
 * Remove a contiguous range of checkers from a checker list.
 * The checkers are freed.
 * The checkers are replaced by a new hole checker.
 * VM allocations are unaffected.
 */
extern void
checker_list_free_range(
	checker_list_t *list,
	entry_checker_range_t range);

/* Convenience function for checker_list_remove_range() of a single checker. */
static inline void
checker_list_free_checker(
	checker_list_t *list,
	vm_entry_checker_t *checker)
{
	checker_list_free_range(list, (entry_checker_range_t){ checker, checker });
}

/*
 * Compute the end address of an entry.
 * `checker->address + checker->size`, with integer overflow protection.
 */
static inline mach_vm_address_t
checker_end_address(vm_entry_checker_t *checker)
{
	mach_vm_address_t end;
	bool overflowed = __builtin_add_overflow(checker->address, checker->size, &end);
	assert(!overflowed);
	return end;
}

/*
 * Return true if address is within checker's [start, end)
 */
static inline bool
checker_contains_address(vm_entry_checker_t *checker, mach_vm_address_t address)
{
	return address >= checker->address && address < checker_end_address(checker);
}

/*
 * Compute the share_mode value of an entry.
 * This value is computed from other values in the checker and its object.
 */
extern uint8_t
checker_share_mode(
	vm_entry_checker_t *checker);

/*
 * Compute the is_submap value of a map entry.
 */
static inline bool
checker_is_submap(vm_entry_checker_t *checker)
{
	return checker->kind == Submap;
}

/*
 * Submap slide (checker_get_and_slide_submap_checkers)
 *
 * We want a 1:1 relationship between checkers and map entries.
 * This is complicated in submaps, where the parent map's view
 * of the submap uses different addresses.
 *
 * Our solution:
 * 1. Submap content checkers store the address as if inside the submap.
 * 2. When using a submap content checker in a parent map context,
 *    the checker is temporarily modified to use parent-relative
 *    addresses instead ("slide").
 *
 * The checker_list_t for the submap keeps track of the slide state
 * of its checkers. Some places assert that the submap is or is not slid.
 *
 * Note that this code only deals with constant submaps; therefore
 * we don't need to worry about changing checker bounds while they
 * are temporarily slid.
 */

/*
 * Return the nested checkers for a parent map's submap entry.
 * Returns NULL if the checker is not a submap entry.
 * The caller must call unslide_submap_checkers() when finished.
 */
extern checker_list_t *
checker_get_and_slide_submap_checkers(vm_entry_checker_t *checker);

/*
 * Undo the effects of get_and_slide_submap_checkers().
 */
extern void
unslide_submap_checkers(checker_list_t *submap_checkers);

/*
 * Convenience macro to call unslide_submap_checkers() at end of scope.
 * The caller may manually unslide and then set their variable to NULL
 * to cancel the automatic unslide.
 */
static inline void
cleanup_unslide_submap_checkers(checker_list_t **inout_submap_checkers)
{
	if (*inout_submap_checkers) {
		unslide_submap_checkers(*inout_submap_checkers);
		*inout_submap_checkers = NULL;
	}
}
#define DEFER_UNSLIDE \
	__attribute__((cleanup(cleanup_unslide_submap_checkers)))


/*
 * Adjust a start/end so that it does not extend beyond a limit.
 * If start/end falls outside the limit, the output's size will
 * be zero and its start will be indeterminate.
 */
extern void
clamp_start_end_to_start_end(
	mach_vm_address_t   * const inout_start,
	mach_vm_address_t   * const inout_end,
	mach_vm_address_t           limit_start,
	mach_vm_address_t           limit_end);


/*
 * Adjust a address/size so that it does not extend beyond a limit.
 * If address/size falls outside the limit, the output size will
 * be zero and the start will be indeterminate
 */
extern void
clamp_address_size_to_address_size(
	mach_vm_address_t   * const inout_address,
	mach_vm_size_t      * const inout_size,
	mach_vm_address_t           limit_address,
	mach_vm_size_t              limit_size);


/*
 * Adjust an address range so it does not extend beyond an entry's bounds.
 * When clamping to a submap entry:
 *   checker is a submap entry in the parent map.
 *   address and size are in the parent map's address space on entry and on exit.
 */
extern void
clamp_address_size_to_checker(
	mach_vm_address_t   * const inout_address,
	mach_vm_size_t      * const inout_size,
	vm_entry_checker_t         *checker);

/*
 * Adjust an address range so it does not extend beyond an entry's bounds.
 * When clamping to a submap entry:
 *   checker is a submap entry in the parent map.
 *   address and size are in the parent map's address space on entry and on exit.
 */
extern void
clamp_start_end_to_checker(
	mach_vm_address_t   * const inout_start,
	mach_vm_address_t   * const inout_end,
	vm_entry_checker_t         *checker);


/*
 * Set the VM object that an entry points to.
 * Replaces any existing object. Updates self_ref_count of any objects.
 */
extern void
checker_set_object(vm_entry_checker_t *checker, vm_object_checker_t *obj_checker);

/*
 * Set an entry's object to the null object.
 * Identical to `checker_set_object(checker, find_object_checker_for_object_id(list, 0))`
 */
extern void
checker_set_null_object(checker_list_t *list, vm_entry_checker_t *checker);

/*
 * Set an entry's object to a copy of its current object,
 * with the new_object->shadow = old_object.
 * The entry's current object must not be null.
 */
extern void
checker_make_shadow_object(checker_list_t *list, vm_entry_checker_t *checker);

/*
 * If checker has a null VM object, change it to a new anonymous object.
 */
extern void
checker_resolve_null_vm_object(
	checker_list_t *checker_list,
	vm_entry_checker_t *checker);

/*
 * Update an entry's checker as if a fault occurred inside it.
 * Assumes that all pages in the entry were faulted.
 * Aborts if the fault appears to be a copy-on-write fault; this code does
 * not attempt to handle that case.
 *
 * - resolves null objects
 * - sets the resident page count
 */
extern void
checker_fault_for_prot_not_cow(
	checker_list_t *checker_list,
	vm_entry_checker_t *checker,
	vm_prot_t fault_prot);


/*
 * Conditionally unnest one checker in a submap.
 *
 * submap_parent is a parent map's submap entry.
 * *inout_next_address is the current address in the parent map,
 *     within the bounds of submap_parent.
 * If the entry inside the submap that contains *inout_next_address is:
 * - unallocated:
 *     advance *inout_next_address past the unallocated space and return NULL
 * - a writeable allocation:
 *     unnest the appropriate range in the parent map,
 *     advance *inout_next_address past the unnested range,
 *     and return the unnested range's new checker
 * - a readable allocation:
 *     - (unnest_readonly == false) advance past it, same as for unallocated holes
 *     - (unnest_readonly == true) unnest it, same as for writeable allocations
 *
 * Set all_overwritten = true if the newly-unnested memory will
 * be promptly written to (thus resolving null objects and collapsing COW shadow chains).
 */
extern vm_entry_checker_t *
checker_list_try_unnest_one_entry_in_submap(
	checker_list_t *checker_list,
	vm_entry_checker_t *submap_parent,
	bool unnest_readonly,
	bool all_overwritten,
	mach_vm_address_t * const inout_next_address);

/*
 * Perform a clip-left operation on a checker, similar to vm_map_clip_left.
 * Entry `right` is divided at `split`.
 * Returns the new left-hand entry.
 * Returns NULL if no split occurred.
 * Updates list->head and/or list->tail if necessary.
 */
extern vm_entry_checker_t *
checker_clip_left(
	checker_list_t *list,
	vm_entry_checker_t *right,
	mach_vm_address_t split);

/*
 * Perform a clip-right operation on a checker, similar to vm_map_clip_right.
 * Entry `left` is divided at `split`.
 * Returns the new right-hand entry.
 * Returns NULL if no split occurred.
 * Updates list->head and/or list->tail if necessary.
 */
extern vm_entry_checker_t *
checker_clip_right(
	checker_list_t *list,
	vm_entry_checker_t *left,
	mach_vm_address_t split);

/*
 * Perform a simplify operation on a checker and the entry to its left.
 * If coalescing occurs, `right` is preserved and
 * the entry to the left is destroyed.
 */
extern void
checker_simplify_left(
	checker_list_t *list,
	vm_entry_checker_t *right);


/*
 * Build a vm_checker for a newly-created memory region.
 * The region is assumed to be the result of vm_allocate().
 * The new checker is not linked into the list.
 */
extern vm_entry_checker_t *
make_checker_for_vm_allocate(
	checker_list_t *list,
	mach_vm_address_t address,
	mach_vm_size_t size,
	int flags_and_tag);

/*
 * Create VM entries and VM entry checkers
 * for the given VM entry templates.
 *
 * Entries will be created consecutively in contiguous memory, as specified.
 * "Holes" will be deallocated during construction;
 *     be warned that the holes may become filled by other allocations
 *     including Rosetta's translations, which will cause the checker to
 *     fail later.
 *
 * Alignment handling:
 *     The first entry gets `alignment_mask` alignment.
 *     After that it is the caller's responsibility to arrange their
 *     templates in a way that yields the alignments they want.
 */
extern __attribute__((overloadable))
checker_list_t *
create_vm_state(
	const vm_entry_template_t entry_templates[],
	unsigned entry_template_count,
	const vm_object_template_t object_templates[],
	unsigned object_template_count,
	mach_vm_size_t alignment_mask,
	const char *message);

static inline __attribute__((overloadable))
checker_list_t *
create_vm_state(
	const vm_entry_template_t templates[],
	unsigned count,
	mach_vm_size_t alignment_mask)
{
	return create_vm_state(templates, count, NULL, 0,
	           alignment_mask, "create_vm_state");
}

/*
 * Like create_vm_state, but the alignment mask defaults to PAGE_MASK
 * and the template list is terminated by END_ENTRIES
 */
static inline __attribute__((overloadable))
checker_list_t *
create_vm_state(const vm_entry_template_t templates[])
{
	return create_vm_state(templates, count_templates(templates), PAGE_MASK);
}

/*
 * Like create_vm_state, but the alignment mask defaults to PAGE_MASK.
 */
static inline __attribute__((overloadable))
checker_list_t *
create_vm_state(const vm_entry_template_t templates[], unsigned count)
{
	return create_vm_state(templates, count, PAGE_MASK);
}


/*
 * Verify that the VM's state (as determined by vm_region)
 * matches the expected state from a list of checkers.
 *
 * Returns TestSucceeded if the state is good, TestFailed otherwise.
 *
 * Failures are also reported as darwintest failures (typically T_FAIL)
 * and failure details of expected and actual state are reported with T_LOG.
 */
extern test_result_t
verify_vm_state(checker_list_t *checker_list, const char *message);

/*
 * Perform VM read and/or write faults on every page spanned by a list of checkers,
 * and verify that exceptions are delivered (or not) as expected.
 * This is a destructive test: the faults may change VM state (for example
 * resolving COW) but the checkers are not updated.
 *
 * Returns TestSucceeded if the state is good, TestFailed otherwise.
 *
 * Failures are also reported as darwintest failures (typically T_FAIL)
 * and failure details of expected and actual state are reported with T_LOG.
 */
extern test_result_t
verify_vm_faultability(
	checker_list_t *checker_list,
	const char *message,
	bool verify_reads,
	bool verify_writes);

/*
 * Like verify_vm_faultability, but reads and/or writes
 * from a single checker's memory.
 * Returns true if the verification succeeded.
 */
extern bool
verify_checker_faultability(
	vm_entry_checker_t *checker,
	const char *message,
	bool verify_reads,
	bool verify_writes);

/*
 * Like verify_checker_faultability, but reads and/or writes
 * only part of a single checker's memory.
 * Returns true if the verification succeeded.
 */
extern bool
verify_checker_faultability_in_address_range(
	vm_entry_checker_t *checker,
	const char *message,
	bool verify_reads,
	bool verify_writes,
	mach_vm_address_t checked_address,
	mach_vm_size_t checked_size);

/*
 * Specification for a single trial:
 * - the test's name
 * - the templates for the virtual memory layout
 * - the address range within that virtual memory
 *   layout that the tested operation should use.
 */
typedef struct vm_config_s {
	char *config_name;

	/*
	 * Test's start address is the start of the first
	 * entry plus start_adjustment. Test's end address
	 * is the end of the last entry plus end_adjustment.
	 * When not zero, start_adjustment is typically positive
	 * and end_adjustment is typically negative.
	 */
	mach_vm_size_t start_adjustment;
	mach_vm_size_t end_adjustment;

	/* First map entry gets this alignment. */
	mach_vm_size_t alignment_mask;

	vm_entry_template_t *entry_templates;
	unsigned entry_template_count;
	vm_object_template_t *object_templates;
	unsigned object_template_count;

	vm_entry_template_t *submap_entry_templates;
	unsigned submap_entry_template_count;
	vm_object_template_t *submap_object_templates;
	unsigned submap_object_template_count;
} vm_config_t;

__attribute__((overloadable))
extern vm_config_t *
make_vm_config(
	const char *name,
	vm_entry_template_t *entry_templates,
	vm_object_template_t *object_templates,
	vm_entry_template_t *submap_entry_templates,
	vm_object_template_t *submap_object_templates,
	mach_vm_size_t start_adjustment,
	mach_vm_size_t end_adjustment,
	mach_vm_size_t alignment_mask);

/*
 * make_vm_config() variants with fewer parameters
 * (convenient for hardcoded initializer syntax)
 *
 * Variants that allow submap entries force submap-compatible alignment.
 * Variants without submap entries use no alignment.
 */

__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
	const char *name,
	vm_entry_template_t *entry_templates,
	vm_object_template_t *object_templates,
	vm_entry_template_t *submap_entry_templates,
	vm_object_template_t *submap_object_templates,
	mach_vm_size_t start_adjustment,
	mach_vm_size_t end_adjustment)
{
	return make_vm_config(name, entry_templates, object_templates,
	           submap_entry_templates, submap_object_templates,
	           start_adjustment, end_adjustment, SUBMAP_ALIGNMENT_MASK);
}

__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
	const char *name,
	vm_entry_template_t *entry_templates,
	vm_object_template_t *object_templates,
	mach_vm_size_t start_adjustment,
	mach_vm_size_t end_adjustment)
{
	return make_vm_config(name, entry_templates, object_templates,
	           NULL, NULL,
	           start_adjustment, end_adjustment, 0);
}

__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
	const char *name,
	vm_entry_template_t *entry_templates,
	mach_vm_size_t start_adjustment,
	mach_vm_size_t end_adjustment)
{
	return make_vm_config(name, entry_templates, NULL,
	           NULL, NULL,
	           start_adjustment, end_adjustment, 0);
}

__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
	const char *name,
	vm_entry_template_t *entry_templates,
	vm_object_template_t *object_templates)
{
	return make_vm_config(name, entry_templates, object_templates,
	           NULL, NULL,
	           0, 0, 0);
}

__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
	const char *name,
	vm_entry_template_t *entry_templates)
{
	return make_vm_config(name, entry_templates, NULL,
	           NULL, NULL,
	           0, 0, 0);
}


/*
 * Like create_vm_state, but also computes the config's desired address range.
 */
extern void
create_vm_state_from_config(
	vm_config_t *config,
	checker_list_t ** const out_checker_list,
	mach_vm_address_t * const out_start_address,
	mach_vm_address_t * const out_end_address);


/*
 * Logs the contents of checkers.
 * Also logs the contents of submap checkers recursively.
 */
extern void
dump_checker_range(entry_checker_range_t list);

/*
 * Logs info from vm_region() for the address ranges spanned by the checkers.
 * Also logs the contents of submaps recursively.
 */
extern void
dump_region_info_for_entries(entry_checker_range_t list);


/*
 * Convenience functions for logging.
 */

extern const char *
name_for_entry_kind(vm_entry_template_kind_t kind);

extern const char *
name_for_kr(kern_return_t kr);

extern const char *
name_for_prot(vm_prot_t prot);

extern const char *
name_for_inherit(vm_inherit_t inheritance);

extern const char *
name_for_behavior(vm_behavior_t behavior);

extern const char *
name_for_bool(boolean_t value);

extern const char *
name_for_share_mode(uint8_t share_mode);

/* Convenience macro for compile-time array size */
#define countof(array)                                                  \
	_Pragma("clang diagnostic push")                                \
	_Pragma("clang diagnostic error \"-Wsizeof-pointer-div\"")      \
	(sizeof(array)/sizeof((array)[0]))                              \
	_Pragma("clang diagnostic pop")

/* Convenience macro for a heap allocated formatted string deallocated at end of scope. */
static inline void
cleanup_cstring(char **ptr)
{
	free(*ptr);
}
#define CLEANUP_CSTRING __attribute__((cleanup(cleanup_cstring)))
#define TEMP_CSTRING(str, format, ...)          \
	char *str CLEANUP_CSTRING;              \
	asprintf(&str, format, __VA_ARGS__)

/*
 * Returns true if each bit set in `values` is also set in `container`.
 */
static inline bool
prot_contains_all(vm_prot_t container, vm_prot_t values)
{
	return (container & values) == values;
}

/*
 * Convenience functions for address arithmetic
 */

static inline mach_vm_address_t
max(mach_vm_address_t a, mach_vm_address_t b)
{
	if (a > b) {
		return a;
	} else {
		return b;
	}
}

static inline mach_vm_address_t
min(mach_vm_address_t a, mach_vm_address_t b)
{
	if (a < b) {
		return a;
	} else {
		return b;
	}
}


/*
 * Call vm_region on an address.
 * If the query address is mapped at that submap depth:
 *   - Sets *inout_address and *out_size to that map entry's address and size.
 *     [*inout_address, *inout_address + *out_size) contains the query address.
 *   - Sets the info from vm_region.
 *   - Returns true.
 * If the query address is unmapped, or not mapped at that submap depth:
 *   - Sets *inout_address to the address of the next map entry, or ~0 if there is none.
 *   - Sets *out_size to zero.
 *   - Returns false.
 */
__attribute__((overloadable))
extern bool
get_info_for_address(
	mach_vm_address_t *inout_address,
	mach_vm_size_t *out_size,
	vm_region_submap_info_data_64_t *out_info,
	uint32_t submap_depth);

__attribute__((overloadable))
static inline bool
get_info_for_address(
	mach_vm_address_t * const inout_address,
	mach_vm_size_t * const out_size,
	vm_region_submap_info_data_64_t * const out_info)
{
	return get_info_for_address(inout_address, out_size, out_info, 0);
}

/*
 * Like get_info_for_address(), but
 * (1) it's faster, and
 * (2) it does not get the right ref_count or shadow_depth values from vm_region.
 */
__attribute__((overloadable))
extern bool
get_info_for_address_fast(
	mach_vm_address_t *inout_address,
	mach_vm_size_t *out_size,
	vm_region_submap_info_data_64_t *out_info,
	uint32_t submap_depth);

__attribute__((overloadable))
static inline bool
get_info_for_address_fast(
	mach_vm_address_t * const inout_address,
	mach_vm_size_t * const out_size,
	vm_region_submap_info_data_64_t * const out_info)
{
	return get_info_for_address_fast(inout_address, out_size, out_info, 0);
}

/*
 * Convenience function to get object_id_full from vm_region at an address.
 * Returns zero if the address is mapped but has a null object.
 * Aborts if the address is not mapped.
 */
extern uint64_t
get_object_id_for_address(mach_vm_address_t address);

/*
 * Convenience function to get user_tag from vm_region at an address.
 * Returns zero if the address is not mapped.
 */
extern uint16_t
get_user_tag_for_address(mach_vm_address_t address);

/*
 * Convenience function to get user_tag from vm_region at an address,
 * if that tag is within the app-specific tag range.
 * Returns zero if the address is not mapped.
 * Returns zero if the address's tag is not within the app-specific range
 * [VM_MEMORY_APPLICATION_SPECIFIC_1, VM_MEMORY_APPLICATION_SPECIFIC_16]
 *
 * This is used by tests that copy user tags from nearby memory.
 * The "nearby" memory might not be part of the tested range.
 * Copying an arbitrary user tag from outside is undesirable
 * because the VM changes some of its behavior for some tag
 * values and the tests need to see consistent behavior instead.
 */
extern uint16_t
get_app_specific_user_tag_for_address(mach_vm_address_t address);

/*
 * Convenience functions for vm_wire's host_priv port.
 * host_priv() returns the port, or halts if it can't.
 * host_priv_allowed() returns true or false.
 * The host_priv port requires root on macOS.
 */
extern host_priv_t
host_priv(void);

extern bool
host_priv_allowed(void);

#endif  /* VM_CONFIGURATOR_H */
