/*
 * 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_test.c
 *
 * Test vm_configurator itself.
 *
 * Verify that the VM states generated by vm_configurator are correct.
 * This is intended to catch bugs in vm_configurator's
 * template and checker system, as well as bugs in individual
 * configurations used by other tests.
 */

#include "configurator/vm_configurator_tests.h"

T_GLOBAL_META(
	T_META_NAMESPACE("xnu.vm.configurator"),
	T_META_RADAR_COMPONENT_NAME("xnu"),
	T_META_RADAR_COMPONENT_VERSION("VM"),
	T_META_RUN_CONCURRENTLY(true),
	T_META_ASROOT(true),  /* required for vm submap sysctls */
	T_META_ALL_VALID_ARCHS(true)
	);


/*
 * Return true if [start, start + size) is a VM entry at the given submap depth.
 */
__attribute__((overloadable))
static bool
is_entry(mach_vm_address_t start, mach_vm_size_t size, uint32_t submap_depth)
{
	mach_vm_address_t entry_start = start;
	mach_vm_size_t entry_size;
	vm_region_submap_info_data_64_t info;

	if (!get_info_for_address(&entry_start, &entry_size, &info, submap_depth)) {
		return false;  /* not mapped */
	}
	if (entry_start != start || entry_size != size) {
		return false;  /* mapped, but wrong extent */
	}

	return true;
}

/*
 * Return true if [start, start + size) is a VM entry at submap depth 0.
 */
__attribute__((overloadable, used))
static bool
is_entry(mach_vm_address_t start, mach_vm_size_t size)
{
	return is_entry(start, size, 0);
}

/*
 * Return true if [start, start + size) is an unallocated hole
 * at the given submap depth.
 */
__attribute__((overloadable))
static bool
is_hole(mach_vm_address_t start, mach_vm_size_t size, uint32_t submap_depth)
{
	mach_vm_address_t entry_start = start;
	mach_vm_size_t entry_size;
	vm_region_submap_info_data_64_t info;

	if (get_info_for_address(&entry_start, &entry_size, &info, submap_depth)) {
		/* start address was mapped */
		return false;
	} else if (entry_start < start + size) {
		/* some address before the end of the expected hole was mapped */
		return false;
	}

	/* [start, start + size) was entirely unmapped */
	return true;
}

/*
 * Return true if [start, start + size) is an unallocated hole at submap depth 0.
 * Unallocate space inside a submap does not count.
 */
__attribute__((overloadable, used))
static bool
is_hole(mach_vm_address_t start, mach_vm_size_t size)
{
	return is_hole(start, size, 0 /* submap_depth */);
}

/*
 * Verify the memory and the checker for an expected hole.
 */
static void
assert_hole_checker_and_entry(
	vm_entry_checker_t *checker,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert(start % PAGE_SIZE == 0);
	assert(size % PAGE_SIZE == 0);
	assert(checker->kind == Hole);
	assert(checker->address == start);
	assert(checker->size == size);

	assert(is_hole(start, size, checker->submap_depth));
}

/*
 * Verify the checker for an expected allocated entry.
 * Does not verify the actual VM state.
 */
static void
assert_allocation_checker(
	vm_entry_checker_t *checker,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert(start % PAGE_SIZE == 0);
	assert(size % PAGE_SIZE == 0);
	assert(checker->kind == Allocation);
	assert(checker->address == start);
	assert(checker->size == size);
}

/*
 * Verify the actual VM state for an expected allocated entry.
 * Does not verify the matching checker.
 * Returns the vm_region output for the memory.
 */
static void
assert_allocation_entry(
	mach_vm_address_t start,
	mach_vm_size_t size,
	uint32_t submap_depth,
	vm_region_submap_info_data_64_t * const out_info)
{
	mach_vm_address_t entry_start = start;
	mach_vm_size_t entry_size;
	assert(get_info_for_address(&entry_start, &entry_size, out_info, submap_depth));
	assert(entry_start == start);
	assert(entry_size == size);
}

/*
 * Verify the memory and the checker for an expected allocated entry.
 * Returns the vm_region output for the memory.
 */
static void
assert_allocation_checker_and_entry(
	vm_entry_checker_t *checker,
	mach_vm_address_t start,
	mach_vm_size_t size,
	vm_region_submap_info_data_64_t * const out_info)
{
	assert_allocation_checker(checker, start, size);
	assert_allocation_entry(start, size, checker->submap_depth, out_info);
}


/*
 * Verify that checker_list consists of a pattern of entries and holes
 * Each allocation or hole is assumed to be DEFAULT_ENTRY_SIZE in length.
 * "##.#..#": allocation, allocation, hole, allocation, hole, hole, allocation
 */
static void
assert_allocation_and_hole_pattern(
	checker_list_t *checker_list,
	const char *pattern)
{
	mach_vm_address_t base = checker_range_start_address(checker_list->entries);
	assert(checker_range_count(checker_list->entries) == strlen(pattern));

	for (size_t i = 0; i < strlen(pattern); i++) {
		vm_region_submap_info_data_64_t info;
		mach_vm_address_t entry_address = base + i * DEFAULT_ENTRY_SIZE;
		vm_entry_checker_t *checker = checker_list_nth(checker_list, i);
		switch (pattern[i]) {
		case '#':
			assert_allocation_checker_and_entry(checker,
			    entry_address, DEFAULT_ENTRY_SIZE, &info);
			break;
		case '.':
			assert_hole_checker_and_entry(checker,
			    entry_address, DEFAULT_ENTRY_SIZE);
			break;
		default:
			T_ASSERT_FAIL("pattern character '%c' is neither '#' nor '.'", pattern[i]);
			break;
		}
	}
}

static void
assert_checker_and_entry_protection_equals(
	vm_entry_checker_t *checker,
	vm_region_submap_info_data_64_t *info,
	int protection,
	int max_protection)
{
	assert(checker->protection == protection);
	assert(checker->max_protection == max_protection);
	assert(info->protection == protection);
	assert(info->max_protection == max_protection);
}

/*
 * Verify the memory and the checker for an expected permanent entry.
 * This is destructive because it attempts to deallocate the permanent entry
 * which makes its memory inaccessible, and updates the checker to match.
 */
static void
destructively_assert_permanent_checker_and_entry(
	vm_entry_checker_t *checker,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert(start % PAGE_SIZE == 0);
	assert(size % PAGE_SIZE == 0);
	assert(checker->permanent == true);
	assert(checker->address == start);
	assert(checker->size == size);

	/*
	 * Permanent memory is indistinguishable in vm_region output.
	 * We can only try to deallocate it and then see if it is still there.
	 */

	/* check that it exists */
	assert(is_entry(start, size, checker->submap_depth));

	/* try to deallocate it */
	kern_return_t kr = mach_vm_deallocate(mach_task_self(), start, size);
	assert(kr == 0);

	/* check that it still exists */
	assert(is_entry(start, size, checker->submap_depth));

	/* update the checker because the memory is now inaccessible */
	checker->protection = VM_PROT_NONE;
	checker->max_protection = VM_PROT_NONE;
}

/*
 * Verify the memory and checker for an expected non-permanent allocation.
 * This is destructive because it deallocates the memory
 * and updates the checker to match.
 */
static void
destructively_assert_nonpermanent_checker_and_entry(
	checker_list_t *list,
	vm_entry_checker_t *checker,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert(start % PAGE_SIZE == 0);
	assert(size % PAGE_SIZE == 0);
	assert(checker->permanent == false);
	assert(checker->address == start);
	assert(checker->size == size);

	/*
	 * Permanent memory is indistinguishable in vm_region output.
	 * We can only try to deallocate it and then see if it is still there.
	 */

	/* check that it exists */
	assert(is_entry(start, size, checker->submap_depth));

	/* try to deallocate it */
	kern_return_t kr = mach_vm_deallocate(mach_task_self(), start, size);
	assert(kr == 0);

	/* check that it no longer exists */
	assert(!is_entry(start, size, checker->submap_depth));
	assert(is_hole(start, size, 0 /* submap_depth */));

	/*
	 * Update the checker to match the now-deallocated memory.
	 * The checker should be replaced by a hole checker.
	 *
	 * Save the checker's index first so we can
	 * look up and verify the hole checker after.
	 */
	unsigned index = 0;
	while (checker_list_nth(list, index) != checker) {
		index++;
	}

	checker_list_free_checker(list, checker);
	vm_entry_checker_t *new_hole = checker_list_nth(list, index);
	assert_hole_checker_and_entry(new_hole, start, size);
}

/*
 * Verify the memory and the checker for an expected submap entry.
 * Does not examine the contents of the submap.
 * Returns the vm_region output for the entry in the parent map.
 */
static void
assert_submap_checker_and_entry(
	vm_entry_checker_t *checker,
	mach_vm_address_t start,
	mach_vm_size_t size,
	vm_region_submap_info_data_64_t * const out_info)
{
	assert(start % PAGE_SIZE == 0);
	assert(size % PAGE_SIZE == 0);
	assert(checker->kind == Submap);
	assert(checker->address == start);
	assert(checker->size == size);
	assert(checker->submap_depth == 0);  /* nested submaps not allowed */

	mach_vm_address_t entry_start = start;
	mach_vm_size_t entry_size;
	assert(get_info_for_address(&entry_start, &entry_size, out_info, checker->submap_depth));
	assert(entry_start == start);
	assert(entry_size == size);
}


static test_result_t
test_single_entry_1(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/* test range is a single allocation */
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;
	assert_allocation_checker_and_entry(checker, start, size, &info);

	return TestSucceeded;
}

static test_result_t
test_single_entry_2(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;

	/* test range excludes the end of the allocation */
	assert(size == DEFAULT_ENTRY_SIZE - DEFAULT_PARTIAL_ENTRY_SIZE);
	assert_allocation_checker_and_entry(checker,
	    start, DEFAULT_ENTRY_SIZE, &info);

	return TestSucceeded;
}

static test_result_t
test_single_entry_3(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;

	/* test range excludes the start of the allocation */
	assert(size == DEFAULT_ENTRY_SIZE - DEFAULT_PARTIAL_ENTRY_SIZE);
	assert_allocation_checker_and_entry(checker,
	    start - DEFAULT_PARTIAL_ENTRY_SIZE, DEFAULT_ENTRY_SIZE, &info);

	return TestSucceeded;
}

static test_result_t
test_single_entry_4(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;

	/* test range excludes the start and end of the allocation */
	assert(size == DEFAULT_ENTRY_SIZE - DEFAULT_PARTIAL_ENTRY_SIZE);
	assert_allocation_checker_and_entry(checker,
	    start - DEFAULT_PARTIAL_ENTRY_SIZE / 2, DEFAULT_ENTRY_SIZE, &info);

	return TestSucceeded;
}


static test_result_t
test_multiple_entries_1(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "##");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 2);
	return TestSucceeded;
}

static test_result_t
test_multiple_entries_2(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "###");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 3);
	return TestSucceeded;
}

static test_result_t
test_multiple_entries_3(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list,
	    "############"
	    "############"
	    "############"
	    "############");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 4 * 12);
	return TestSucceeded;
}

static test_result_t
test_multiple_entries_4(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "###");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 3 - DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_multiple_entries_5(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "###");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_PARTIAL_ENTRY_SIZE);
	assert(size == DEFAULT_ENTRY_SIZE * 3 - DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_multiple_entries_6(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "###");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_PARTIAL_ENTRY_SIZE / 2);
	assert(size == DEFAULT_ENTRY_SIZE * 3 - DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}


static test_result_t
test_some_holes_1(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, ".#");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 2);
	return TestSucceeded;
}

static test_result_t
test_some_holes_2(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, ".###");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 4);
	return TestSucceeded;
}

static test_result_t
test_some_holes_3(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, ".#");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 2 - DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_some_holes_4(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, ".###");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 4 - DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_some_holes_5(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "#.");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 2);
	return TestSucceeded;
}

static test_result_t
test_some_holes_6(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "###.");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 4);
	return TestSucceeded;
}

static test_result_t
test_some_holes_7(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "#.");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_PARTIAL_ENTRY_SIZE);
	assert(size == DEFAULT_ENTRY_SIZE * 2 - DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_some_holes_8(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "###.");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_PARTIAL_ENTRY_SIZE);
	assert(size == DEFAULT_ENTRY_SIZE * 4 - DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_some_holes_9(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "#.#");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 3);
	return TestSucceeded;
}

static test_result_t
test_some_holes_10(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "#.#.#");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 5);
	return TestSucceeded;
}

static test_result_t
test_some_holes_11(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "##.##.##");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 8);
	return TestSucceeded;
}

static test_result_t
test_some_holes_12(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "###.###.###");
	assert(start == checker_range_start_address(checker_list->entries));
	assert(size == DEFAULT_ENTRY_SIZE * 11);
	return TestSucceeded;
}


static test_result_t
test_all_holes_1(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "#.#");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_ENTRY_SIZE);
	assert(size == DEFAULT_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_all_holes_2(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, "#.");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_ENTRY_SIZE);
	assert(size == DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_all_holes_3(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, ".#");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_PARTIAL_ENTRY_SIZE);
	assert(size == DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_all_holes_4(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_allocation_and_hole_pattern(checker_list, ".");
	assert(start == checker_range_start_address(checker_list->entries) + DEFAULT_PARTIAL_ENTRY_SIZE / 2);
	assert(size == DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}


static test_result_t
test_null_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;
	assert_allocation_checker_and_entry(checker, start, size, &info);

	/* entry's object is null */
	assert(info.object_id_full == 0);
	assert(checker->object->object_id == 0);

	return TestSucceeded;
}

static test_result_t
test_nonresident_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;
	assert_allocation_checker_and_entry(checker, start, size, &info);

	/* entry has an object, but its pages are not resident */
	assert(info.object_id_full != 0);
	assert(info.pages_resident == 0);

	assert(checker->object->object_id != 0);
	assert(checker->pages_resident == 0);

	return TestSucceeded;
}

static test_result_t
test_resident_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;
	assert_allocation_checker_and_entry(checker, start, size, &info);

	/* entry has an object and its pages are resident */
	assert(info.pages_resident == size / PAGE_SIZE);
	assert(checker->pages_resident == size / PAGE_SIZE);
	assert(checker->object->object_id != 0);

	return TestSucceeded;
}

/* common code for two-shared-entry tests */
static void
test_one_shared_pair(
	checker_list_t *checker_list,
	mach_vm_address_t left_entry_start,
	mach_vm_address_t right_entry_start,
	mach_vm_size_t size,
	mach_vm_address_t right_object_offset)
{
	/*
	 * Two entries, both have the same object with refcount two.
	 * Right entry's object offset varies.
	 */
	vm_entry_checker_t *left_checker =
	    checker_list_find_allocation(checker_list, left_entry_start);
	vm_entry_checker_t *right_checker =
	    checker_list_find_allocation(checker_list, right_entry_start);
	assert(left_checker);
	assert(right_checker);

	vm_region_submap_info_data_64_t left_info, right_info;
	assert_allocation_checker_and_entry(left_checker, left_entry_start, size, &left_info);
	assert_allocation_checker_and_entry(right_checker, right_entry_start, size, &right_info);

	assert(left_info.object_id_full != 0);
	assert(left_info.object_id_full == right_info.object_id_full);
	assert(left_info.ref_count == 2);
	assert(right_info.ref_count == 2);
	assert(left_info.share_mode == SM_TRUESHARED);
	assert(right_info.share_mode == SM_TRUESHARED);
	assert(left_info.offset == 0);
	assert(right_info.offset == right_object_offset);
	assert(left_info.user_tag != right_info.user_tag);

	assert(left_checker->object == right_checker->object);
	/* checker doesn't distinguish SM_SHARED from SM_TRUESHARED */
	assert(checker_share_mode(left_checker) == SM_SHARED);
	assert(checker_share_mode(right_checker) == SM_SHARED);
	assert(left_checker->object_offset == 0);
	assert(right_checker->object_offset == right_object_offset);
	assert(left_checker->user_tag != right_checker->user_tag);
}

static test_result_t
test_shared_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/* entries are both at object offset 0 */
	test_one_shared_pair(checker_list, start, start + size, size, 0);
	return TestSucceeded;
}

static test_result_t
test_shared_entry_discontiguous(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * right entry's object offset begins
	 * after the left entry's range ends
	 */
	test_one_shared_pair(checker_list, start, start + size, size, DEFAULT_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_shared_entry_partial(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * right entry's object offset begins
	 * inside the left entry's range
	 */
	test_one_shared_pair(checker_list, start, start + size, size, DEFAULT_PARTIAL_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_shared_entry_pairs(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * two shared pairs
	 */
	mach_vm_size_t entry_size = size / 4;
	mach_vm_address_t one = start;
	mach_vm_address_t two = one + entry_size;
	mach_vm_address_t three = two + entry_size;
	mach_vm_address_t four = three + entry_size;

	test_one_shared_pair(checker_list, one, four, entry_size, 0);
	test_one_shared_pair(checker_list, two, three, entry_size, 0);

	return TestSucceeded;
}

static test_result_t
test_shared_entry_x1000(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/* many entries, all of which share the same object */

	entry_checker_range_t limit = checker_list_find_range(checker_list, start, size);
	assert(checker_range_count(limit) == 1000);

	uint64_t shared_object_id = 0;
	FOREACH_CHECKER(checker, limit) {
		assert(checker->object);
		assert(checker->object->object_id_mode == object_has_known_id);
		if (!shared_object_id) {
			assert(checker->object->object_id != 0);
			shared_object_id = checker->object->object_id;
		}
		assert(checker->object->object_id == shared_object_id);
		assert(get_object_id_for_address(checker->address) == shared_object_id);
	}

	return TestSucceeded;
}


/* common code for two-shared-entry tests */
static test_result_t
test_cow_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * two entries, both have the same object and its refcount is two.
	 * [start, start + size) is only the first entry
	 */
	vm_entry_checker_t *left_checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t left_info;
	assert_allocation_checker_and_entry(left_checker, start, size, &left_info);

	vm_entry_checker_t *right_checker = checker_list_nth(checker_list, 1);
	vm_region_submap_info_data_64_t right_info;
	assert_allocation_checker_and_entry(right_checker, start + size, size, &right_info);

	assert(left_info.object_id_full != 0);
	assert(left_info.object_id_full == right_info.object_id_full);
	assert(left_info.ref_count == 2);
	assert(right_info.ref_count == 2);
	assert(left_info.share_mode == SM_COW);
	assert(right_info.share_mode == SM_COW);
	assert(left_info.offset == 0);
	assert(right_info.offset == 0);

	assert(left_checker->object == right_checker->object);
	assert(checker_share_mode(left_checker) == SM_COW);
	assert(checker_share_mode(right_checker) == SM_COW);
	assert(left_checker->object_offset == 0);
	assert(right_checker->object_offset == 0);

	return TestSucceeded;
}

static test_result_t
test_cow_unreferenced(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * one COW entry with refcount 1
	 */
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;
	assert_allocation_checker_and_entry(checker, start, size, &info);

	assert(info.share_mode == SM_COW);
	assert(info.object_id_full != 0);
	assert(info.ref_count == 1);
	assert(info.offset == 0);

	assert(checker->object_offset == 0);
	assert(checker_share_mode(checker) == SM_COW);
	assert(checker->object);
	assert(checker->object->self_ref_count == 1);

	return TestSucceeded;
}

/* common checks for cow_nocow and nocow_cow */
static test_result_t
test_cow_nocow_common(
	vm_entry_checker_t *cow_checker,
	vm_entry_checker_t *plain_checker,
	mach_vm_address_t cow_start_address,
	mach_vm_address_t plain_start_address,
	mach_vm_size_t entry_size)
{
	/* two entries: one is COW, one is not COW */
	vm_region_submap_info_data_64_t cow_info, plain_info;
	assert_allocation_checker_and_entry(cow_checker, cow_start_address, entry_size, &cow_info);
	assert_allocation_checker_and_entry(plain_checker, plain_start_address, entry_size, &plain_info);

	assert(cow_info.share_mode == SM_COW);
	assert(plain_info.share_mode == SM_PRIVATE);
	assert(cow_info.object_id_full != 0);
	assert(cow_info.object_id_full != plain_info.object_id_full);
	assert(cow_info.ref_count == 2);
	assert(cow_info.offset == 0);

	assert(checker_share_mode(cow_checker) == SM_COW);
	assert(checker_share_mode(plain_checker) == SM_PRIVATE);
	assert(cow_checker->object != NULL);
	assert(cow_checker->object != plain_checker->object);
	assert(cow_checker->object_offset == 0);

	return TestSucceeded;
}

static test_result_t
test_cow_nocow(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * two entries: first is COW, second is not
	 */
	vm_entry_checker_t *cow_checker   = checker_list_nth(checker_list, 0);
	vm_entry_checker_t *plain_checker = checker_list_nth(checker_list, 1);

	assert(size % 2 == 0);
	mach_vm_address_t cow_start = start;
	mach_vm_address_t plain_start = start + size / 2;

	return test_cow_nocow_common(cow_checker, plain_checker,
	           cow_start, plain_start, size / 2);
}

static test_result_t
test_nocow_cow(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * two entries: first is not COW, second is COW
	 */
	vm_entry_checker_t *plain_checker = checker_list_nth(checker_list, 0);
	vm_entry_checker_t *cow_checker   = checker_list_nth(checker_list, 1);

	assert(size % 2 == 0);
	mach_vm_address_t plain_start = start;
	mach_vm_address_t cow_start = start + size / 2;

	return test_cow_nocow_common(cow_checker, plain_checker,
	           cow_start, plain_start, size / 2);
}

static test_result_t
test_cow_unreadable(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * COW entry, unreadable
	 */
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;
	assert_allocation_checker_and_entry(checker, start, size, &info);

	assert(info.share_mode == SM_COW);
	assert(info.protection == VM_PROT_NONE);
	assert(info.ref_count == 2);

	assert(checker_share_mode(checker) == SM_COW);
	assert(checker->protection == VM_PROT_NONE);
	assert(checker->object != NULL);
	assert(checker->object->self_ref_count == 2);

	return TestSucceeded;
}

static test_result_t
test_cow_unwriteable(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * COW entry, readable but unwriteable
	 */
	vm_entry_checker_t *checker = checker_list_nth(checker_list, 0);
	vm_region_submap_info_data_64_t info;
	assert_allocation_checker_and_entry(checker, start, size, &info);

	assert(info.share_mode == SM_COW);
	assert(info.protection == VM_PROT_READ);
	assert(info.ref_count == 2);

	assert(checker_share_mode(checker) == SM_COW);
	assert(checker->protection == VM_PROT_READ);
	assert(checker->object != NULL);
	assert(checker->object->self_ref_count == 2);

	return TestSucceeded;
}

static test_result_t
test_permanent_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 0), start, size);

	return TestSucceeded;
}

static test_result_t
test_permanent_before_permanent(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 0),
		start, size / 2);
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 1),
		start + size / 2, size / 2);

	return TestSucceeded;
}

static test_result_t
test_permanent_before_allocation(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 0),
		start, size / 2);
	destructively_assert_nonpermanent_checker_and_entry(
		checker_list,
		checker_list_nth(checker_list, 1),
		start + size / 2, size / 2);
	assert_hole_checker_and_entry(
		checker_list_nth(checker_list, 2),
		start + size, DEFAULT_ENTRY_SIZE);
	return TestSucceeded;
}

static test_result_t
test_permanent_before_allocation_2(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_region_submap_info_data_64_t info;
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 0),
		start, size / 2);
	destructively_assert_nonpermanent_checker_and_entry(
		checker_list,
		checker_list_nth(checker_list, 1),
		start + size / 2, size / 2);
	assert_allocation_checker_and_entry(
		checker_list_nth(checker_list, 2),
		start + size, DEFAULT_ENTRY_SIZE, &info);

	return TestSucceeded;
}

static test_result_t
test_permanent_before_hole(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 0),
		start, size / 2);
	assert_hole_checker_and_entry(
		checker_list_nth(checker_list, 1),
		start + size / 2, size / 2);

	return TestSucceeded;
}

static test_result_t
test_permanent_after_allocation(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	destructively_assert_nonpermanent_checker_and_entry(
		checker_list,
		checker_list_nth(checker_list, 0),
		start, size / 2);
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 1),
		start + size / 2, size / 2);

	return TestSucceeded;
}

static test_result_t
test_permanent_after_hole(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	assert_hole_checker_and_entry(
		checker_list_nth(checker_list, 0),
		start, size / 2);
	destructively_assert_permanent_checker_and_entry(
		checker_list_nth(checker_list, 1),
		start + size / 2, size / 2);

	return TestSucceeded;
}


static test_result_t
test_single_submap_single_entry_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_region_submap_info_data_64_t info;

	vm_entry_checker_t *submap_parent = checker_list_nth(checker_list, 0);
	assert_submap_checker_and_entry(submap_parent, start, size, &info);

	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(submap_parent);

	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker_and_entry(submap_content, start, size, &info);

	return TestSucceeded;
}

static test_result_t
test_single_submap_single_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_single_submap_single_entry_common(
		checker_list, start, size);
}

static test_result_t
test_single_submap_single_entry_first_pages(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/* tested range excludes the last 1/2 of the real entry */
	mach_vm_size_t submap_size = size * 2;
	mach_vm_address_t submap_start = start;
	return test_single_submap_single_entry_common(
		checker_list, submap_start, submap_size);
}

static test_result_t
test_single_submap_single_entry_last_pages(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/* tested range excludes the first 1/2 of the real entry */
	mach_vm_size_t submap_size = size * 2;
	mach_vm_address_t submap_start = start - submap_size / 2;
	return test_single_submap_single_entry_common(
		checker_list, submap_start, submap_size);
}

static test_result_t
test_single_submap_single_entry_middle_pages(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/* tested range excludes the first 1/4 and last 1/4 of the real entry */
	mach_vm_size_t submap_size = size * 2;
	mach_vm_address_t submap_start = start - submap_size / 4;
	return test_single_submap_single_entry_common(
		checker_list, submap_start, submap_size);
}

static test_result_t
test_single_submap_oversize_entry_common(
	checker_list_t *checker_list,
	mach_vm_address_t parent_start,
	mach_vm_size_t parent_size,
	mach_vm_address_t parent_offset,
	mach_vm_size_t submap_size)
{
	vm_region_submap_info_data_64_t parent_info, content_info;

	vm_entry_checker_t *submap_parent = checker_list_nth(checker_list, 0);
	assert_submap_checker_and_entry(submap_parent, parent_start, parent_size, &parent_info);
	assert(submap_parent->object_offset == parent_offset);
	assert(parent_info.offset == parent_offset);

	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(submap_parent);

	/*
	 * Actual entry in submap is clamped to the parent map submap view
	 * by vm_region. Checker for that entry is unchanged.
	 */
	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, parent_start - parent_offset, submap_size);
	assert(submap_content->submap_depth == 1);
	assert_allocation_entry(parent_start, parent_size, 1 /* submap_depth */, &content_info);
	assert(submap_content->object_offset == 0);
	assert(content_info.offset == 0);

	return TestSucceeded;
}

static test_result_t
test_single_submap_oversize_entry_at_start(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * parent map:                     [start, start+size]
	 * submap:       [0                (size)      size*2]
	 */
	return test_single_submap_oversize_entry_common(checker_list,
	           start, size,
	           size /* parent_offset */, size * 2 /* submap_size */);
}

static test_result_t
test_single_submap_oversize_entry_at_end(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * parent map:   [start, start+size]
	 * submap:       [0                (size)      size*2]
	 */
	return test_single_submap_oversize_entry_common(checker_list,
	           start, size,
	           0 /* parent_offset */, size * 2 /* submap_size */);
}

static test_result_t
test_single_submap_oversize_entry_at_both(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	/*
	 * parent map:            [start, start+size]
	 * submap:       [0       (size / 2)           size*2]
	 */
	return test_single_submap_oversize_entry_common(checker_list,
	           start, size,
	           size / 2 /* parent_offset */, size * 2 /* submap_size */);
}

static test_result_t
test_submap_before_allocation_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size,
	int submap_protection)
{
	vm_region_submap_info_data_64_t submap_parent_info;
	vm_entry_checker_t *submap_parent = checker_list_nth(checker_list, 0);
	assert_submap_checker_and_entry(submap_parent,
	    start, size / 2, &submap_parent_info);

	vm_region_submap_info_data_64_t allocation_info;
	vm_entry_checker_t *allocation = checker_list_nth(checker_list, 1);
	assert_allocation_checker_and_entry(allocation,
	    start + size / 2, size / 2, &allocation_info);

	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(submap_parent);

	vm_region_submap_info_data_64_t submap_content_info;
	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, start, size / 2);
	assert_allocation_entry(start, size / 2, 1 /* submap_depth */, &submap_content_info);
	assert_checker_and_entry_protection_equals(submap_content, &submap_content_info,
	    submap_protection, submap_protection);

	return TestSucceeded;
}

static test_result_t
test_submap_before_allocation(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_before_allocation_common(checker_list,
	           start, size, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_submap_before_allocation_ro(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_before_allocation_common(checker_list,
	           start, size, VM_PROT_READ);
}

static test_result_t
test_submap_after_allocation_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size,
	int submap_protection)
{
	vm_region_submap_info_data_64_t allocation_info;
	vm_entry_checker_t *allocation = checker_list_nth(checker_list, 0);
	assert_allocation_checker_and_entry(allocation,
	    start, size / 2, &allocation_info);

	vm_region_submap_info_data_64_t submap_parent_info;
	vm_entry_checker_t *submap_parent = checker_list_nth(checker_list, 1);
	assert_submap_checker_and_entry(submap_parent,
	    start + size / 2, size / 2, &submap_parent_info);

	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(submap_parent);

	vm_region_submap_info_data_64_t submap_content_info;
	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, start + size / 2, size / 2);
	assert_allocation_entry(start + size / 2, size / 2, 1 /* submap_depth */, &submap_content_info);
	assert_checker_and_entry_protection_equals(submap_content, &submap_content_info,
	    submap_protection, submap_protection);

	return TestSucceeded;
}

static test_result_t
test_submap_after_allocation(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_after_allocation_common(checker_list,
	           start, size, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_submap_after_allocation_ro(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_after_allocation_common(checker_list,
	           start, size, VM_PROT_READ);
}

static test_result_t
test_submap_before_hole_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size,
	int submap_protection)
{
	vm_region_submap_info_data_64_t submap_parent_info;
	vm_entry_checker_t *submap_parent = checker_list_nth(checker_list, 0);
	assert_submap_checker_and_entry(submap_parent,
	    start, size / 2, &submap_parent_info);

	vm_entry_checker_t *hole = checker_list_nth(checker_list, 1);
	assert_hole_checker_and_entry(hole,
	    start + size / 2, size / 2);

	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(submap_parent);

	vm_region_submap_info_data_64_t submap_content_info;
	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, start, size / 2);
	assert_allocation_entry(start, size / 2, 1 /* submap_depth */, &submap_content_info);
	assert_checker_and_entry_protection_equals(submap_content, &submap_content_info,
	    submap_protection, submap_protection);

	return TestSucceeded;
}

static test_result_t
test_submap_before_hole(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_before_hole_common(checker_list,
	           start, size, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_submap_before_hole_ro(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_before_hole_common(checker_list,
	           start, size, VM_PROT_READ);
}

static test_result_t
test_submap_after_hole_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size,
	int submap_protection)
{
	vm_entry_checker_t *hole = checker_list_nth(checker_list, 0);
	assert_hole_checker_and_entry(hole,
	    start, size / 2);

	vm_region_submap_info_data_64_t submap_parent_info;
	vm_entry_checker_t *submap_parent = checker_list_nth(checker_list, 1);
	assert_submap_checker_and_entry(submap_parent,
	    start + size / 2, size / 2, &submap_parent_info);

	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(submap_parent);

	vm_region_submap_info_data_64_t submap_content_info;
	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, start + size / 2, size / 2);
	assert_allocation_entry(start + size / 2, size / 2, 1 /* submap_depth */, &submap_content_info);
	assert_checker_and_entry_protection_equals(submap_content, &submap_content_info,
	    submap_protection, submap_protection);

	return TestSucceeded;
}

static test_result_t
test_submap_after_hole(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_after_hole_common(checker_list,
	           start, size, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_submap_after_hole_ro(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_after_hole_common(checker_list,
	           start, size, VM_PROT_READ);
}


/*
 * Verify that the checker list consists of three entries,
 * a submap mapping, an allocation, and a submap mapping,
 * all of default size.
 */
static void
assert_submap_allocation_submap(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	vm_region_submap_info_data_64_t info;
	vm_entry_checker_t *checker;
	mach_vm_size_t offset;

	assert(checker_range_count(checker_list->entries) == 3);

	offset = DEFAULT_ENTRY_SIZE * 0;
	checker = checker_list_nth(checker_list, 0);
	assert_submap_checker_and_entry(checker,
	    start + offset, DEFAULT_ENTRY_SIZE, &info);
	assert(checker->object_offset == offset);
	assert(info.offset == offset);

	offset = DEFAULT_ENTRY_SIZE * 1;
	checker = checker_list_nth(checker_list, 1);
	assert_allocation_checker_and_entry(checker,
	    start + offset, DEFAULT_ENTRY_SIZE, &info);

	offset = DEFAULT_ENTRY_SIZE * 2;
	checker = checker_list_nth(checker_list, 2);
	assert_submap_checker_and_entry(checker,
	    start + offset, DEFAULT_ENTRY_SIZE, &info);
	assert(checker->object_offset == offset);
	assert(info.offset == offset);

	offset = DEFAULT_ENTRY_SIZE * 3;
	assert(size == offset);
}

static test_result_t
test_submap_allocation_submap_one_entry_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size,
	int submap_protection)
{
	/* parent map is submap - allocation - submap */
	assert_submap_allocation_submap(checker_list, start, size);

	/* submap is one allocation entry */
	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(checker_list_nth(checker_list, 0));
	assert(checker_range_count(submap_checkers->entries) == 1);
	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, start, size);
	assert(submap_content->protection == submap_protection);
	assert(submap_content->max_protection == submap_protection);

	return TestSucceeded;
}

static test_result_t
test_submap_allocation_submap_one_entry(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_allocation_submap_one_entry_common(checker_list,
	           start, size, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_submap_allocation_submap_one_entry_ro(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_allocation_submap_one_entry_common(checker_list,
	           start, size, VM_PROT_READ);
}

static test_result_t
test_submap_allocation_submap_two_entries_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size,
	int submap_protection)
{
	/* parent map is submap - allocation - submap */
	assert_submap_allocation_submap(checker_list, start, size);

	/* submap is two allocation entries */
	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(checker_list_nth(checker_list, 0));
	assert(checker_range_count(submap_checkers->entries) == 2);

	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, start, size / 2);
	assert(submap_content->protection == submap_protection);
	assert(submap_content->max_protection == submap_protection);

	submap_content = checker_list_nth(submap_checkers, 1);
	assert_allocation_checker(submap_content, start + size / 2, size / 2);
	assert(submap_content->protection == submap_protection);
	assert(submap_content->max_protection == submap_protection);

	return TestSucceeded;
}

static test_result_t
test_submap_allocation_submap_two_entries(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_allocation_submap_two_entries_common(checker_list,
	           start, size, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_submap_allocation_submap_two_entries_ro(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_allocation_submap_two_entries_common(checker_list,
	           start, size, VM_PROT_READ);
}

static test_result_t
test_submap_allocation_submap_three_entries_common(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size,
	int submap_protection)
{
	/* parent map is submap - allocation - submap */
	assert_submap_allocation_submap(checker_list, start, size);

	/* submap is three allocation entries */
	checker_list_t *submap_checkers DEFER_UNSLIDE =
	    checker_get_and_slide_submap_checkers(checker_list_nth(checker_list, 0));
	assert(checker_range_count(submap_checkers->entries) == 3);

	vm_entry_checker_t *submap_content = checker_list_nth(submap_checkers, 0);
	assert_allocation_checker(submap_content, start, size / 3);
	assert(submap_content->protection == submap_protection);
	assert(submap_content->max_protection == submap_protection);

	submap_content = checker_list_nth(submap_checkers, 1);
	assert_allocation_checker(submap_content, start + size / 3, size / 3);
	assert(submap_content->protection == submap_protection);
	assert(submap_content->max_protection == submap_protection);

	submap_content = checker_list_nth(submap_checkers, 2);
	assert_allocation_checker(submap_content, start + size / 3 * 2, size / 3);
	assert(submap_content->protection == submap_protection);
	assert(submap_content->max_protection == submap_protection);

	return TestSucceeded;
}

static test_result_t
test_submap_allocation_submap_three_entries(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_allocation_submap_three_entries_common(checker_list,
	           start, size, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_submap_allocation_submap_three_entries_ro(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size)
{
	return test_submap_allocation_submap_three_entries_common(checker_list,
	           start, size, VM_PROT_READ);
}


static void
assert_protection(
	mach_vm_address_t address,
	vm_prot_t prot,
	bool check_max,
	uint32_t submap_depth)
{
	mach_vm_address_t info_address = address;
	mach_vm_size_t info_size;
	vm_region_submap_info_data_64_t info;
	assert(get_info_for_address(&info_address, &info_size, &info, submap_depth));
	assert(info_address == address);

	if (check_max) {
		T_QUIET; T_ASSERT_EQ(prot, info.max_protection, "entry max protection");
	} else {
		T_QUIET; T_ASSERT_EQ(prot, info.protection, "entry protection");
	}
}

static test_result_t
test_protection_single_common(
	checker_list_t *checker_list,
	mach_vm_address_t address,
	vm_prot_t prot, vm_prot_t max)
{
	vm_entry_checker_t *checker =
	    checker_list_find_allocation(checker_list, address);
	T_QUIET; T_ASSERT_NOTNULL(checker, "checker");
	T_QUIET; T_ASSERT_EQ(checker->protection, prot, "checker protection");
	T_QUIET; T_ASSERT_EQ(checker->max_protection, max, "checker max protection");

	assert_protection(address, prot, false /* check max */, 0 /* submap depth */);
	assert_protection(address, max, true /* check max */, 0 /* submap depth */);

	return TestSucceeded;
}

static test_result_t
test_protection_pair_common(
	checker_list_t *checker_list,
	mach_vm_address_t address,
	vm_prot_t left_prot,
	vm_prot_t right_prot)
{
	vm_entry_checker_t *left_checker =
	    checker_list_find_allocation(checker_list, address);
	vm_entry_checker_t *right_checker = left_checker->next;

	T_QUIET; T_ASSERT_NOTNULL(left_checker, "checker");
	T_QUIET; T_ASSERT_EQ(left_checker->protection, left_prot, "left entry protection");
	T_QUIET; T_ASSERT_EQ(right_checker->protection, right_prot, "right entry protection");

	assert_protection(left_checker->address, left_prot, false /* check max */, 0 /* submap depth */);
	assert_protection(right_checker->address, right_prot, false /* check max */, 0 /* submap depth */);

	return TestSucceeded;
}

static test_result_t
test_protection_single_000_000(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_NONE);
}

static test_result_t
test_protection_single_000_r00(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_READ);
}

static test_result_t
test_protection_single_000_0w0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_WRITE);
}

static test_result_t
test_protection_single_000_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_protection_single_r00_r00(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_READ, VM_PROT_READ);
}

static test_result_t
test_protection_single_r00_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_READ, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_protection_single_0w0_0w0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_WRITE, VM_PROT_WRITE);
}

static test_result_t
test_protection_single_0w0_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_protection_single_rw0_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_single_common(
		checker_list, start, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE);
}


static test_result_t
test_protection_pair_000_000(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_NONE);
}

static test_result_t
test_protection_pair_000_r00(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_READ);
}

static test_result_t
test_protection_pair_000_0w0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_WRITE);
}

static test_result_t
test_protection_pair_000_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_NONE, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_protection_pair_r00_000(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ, VM_PROT_NONE);
}

static test_result_t
test_protection_pair_r00_r00(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ, VM_PROT_READ);
}

static test_result_t
test_protection_pair_r00_0w0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ, VM_PROT_WRITE);
}

static test_result_t
test_protection_pair_r00_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_protection_pair_0w0_000(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_WRITE, VM_PROT_NONE);
}

static test_result_t
test_protection_pair_0w0_r00(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_WRITE, VM_PROT_READ);
}

static test_result_t
test_protection_pair_0w0_0w0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_WRITE, VM_PROT_WRITE);
}

static test_result_t
test_protection_pair_0w0_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE);
}

static test_result_t
test_protection_pair_rw0_000(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_NONE);
}

static test_result_t
test_protection_pair_rw0_r00(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ);
}

static test_result_t
test_protection_pair_rw0_0w0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_WRITE);
}

static test_result_t
test_protection_pair_rw0_rw0(
	checker_list_t *checker_list,
	mach_vm_address_t start,
	mach_vm_size_t size __unused)
{
	return test_protection_pair_common(
		checker_list, start, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE);
}


T_DECL(test_vm_configurator,
    "spot-check VM states generated by vm configurator")
{
	vm_tests_t tests = {
		.single_entry_1 = test_single_entry_1,
		.single_entry_2 = test_single_entry_2,
		.single_entry_3 = test_single_entry_3,
		.single_entry_4 = test_single_entry_4,

		.multiple_entries_1 = test_multiple_entries_1,
		.multiple_entries_2 = test_multiple_entries_2,
		.multiple_entries_3 = test_multiple_entries_3,
		.multiple_entries_4 = test_multiple_entries_4,
		.multiple_entries_5 = test_multiple_entries_5,
		.multiple_entries_6 = test_multiple_entries_6,

		.some_holes_1 = test_some_holes_1,
		.some_holes_2 = test_some_holes_2,
		.some_holes_3 = test_some_holes_3,
		.some_holes_4 = test_some_holes_4,
		.some_holes_5 = test_some_holes_5,
		.some_holes_6 = test_some_holes_6,
		.some_holes_7 = test_some_holes_7,
		.some_holes_8 = test_some_holes_8,
		.some_holes_9 = test_some_holes_9,
		.some_holes_10 = test_some_holes_10,
		.some_holes_11 = test_some_holes_11,
		.some_holes_12 = test_some_holes_12,

		.all_holes_1 = test_all_holes_1,
		.all_holes_2 = test_all_holes_2,
		.all_holes_3 = test_all_holes_3,
		.all_holes_4 = test_all_holes_4,

		.null_entry        = test_null_entry,
		.nonresident_entry = test_nonresident_entry,
		.resident_entry    = test_resident_entry,

		.shared_entry               = test_shared_entry,
		.shared_entry_discontiguous = test_shared_entry_discontiguous,
		.shared_entry_partial       = test_shared_entry_partial,
		.shared_entry_pairs         = test_shared_entry_pairs,
		.shared_entry_x1000         = test_shared_entry_x1000,

		.cow_entry = test_cow_entry,
		.cow_unreferenced = test_cow_unreferenced,
		.cow_nocow = test_cow_nocow,
		.nocow_cow = test_nocow_cow,
		.cow_unreadable = test_cow_unreadable,
		.cow_unwriteable = test_cow_unwriteable,

		.permanent_entry = test_permanent_entry,
		.permanent_before_permanent = test_permanent_before_permanent,
		.permanent_before_allocation = test_permanent_before_allocation,
		.permanent_before_allocation_2 = test_permanent_before_allocation_2,
		.permanent_before_hole = test_permanent_before_hole,
		.permanent_after_allocation = test_permanent_after_allocation,
		.permanent_after_hole = test_permanent_after_hole,

		.single_submap_single_entry = test_single_submap_single_entry,
		.single_submap_single_entry_first_pages = test_single_submap_single_entry_first_pages,
		.single_submap_single_entry_last_pages = test_single_submap_single_entry_last_pages,
		.single_submap_single_entry_middle_pages = test_single_submap_single_entry_middle_pages,
		.single_submap_oversize_entry_at_start = test_single_submap_oversize_entry_at_start,
		.single_submap_oversize_entry_at_end = test_single_submap_oversize_entry_at_end,
		.single_submap_oversize_entry_at_both = test_single_submap_oversize_entry_at_both,

		.submap_before_allocation = test_submap_before_allocation,
		.submap_after_allocation = test_submap_after_allocation,
		.submap_before_hole = test_submap_before_hole,
		.submap_after_hole = test_submap_after_hole,
		.submap_allocation_submap_one_entry = test_submap_allocation_submap_one_entry,
		.submap_allocation_submap_two_entries = test_submap_allocation_submap_two_entries,
		.submap_allocation_submap_three_entries = test_submap_allocation_submap_three_entries,

		.submap_before_allocation_ro = test_submap_before_allocation_ro,
		.submap_after_allocation_ro = test_submap_after_allocation_ro,
		.submap_before_hole_ro = test_submap_before_hole_ro,
		.submap_after_hole_ro = test_submap_after_hole_ro,
		.submap_allocation_submap_one_entry_ro = test_submap_allocation_submap_one_entry_ro,
		.submap_allocation_submap_two_entries_ro = test_submap_allocation_submap_two_entries_ro,
		.submap_allocation_submap_three_entries_ro = test_submap_allocation_submap_three_entries_ro,

		.protection_single_000_000 = test_protection_single_000_000,
		.protection_single_000_r00 = test_protection_single_000_r00,
		.protection_single_000_0w0 = test_protection_single_000_0w0,
		.protection_single_000_rw0 = test_protection_single_000_rw0,
		.protection_single_r00_r00 = test_protection_single_r00_r00,
		.protection_single_r00_rw0 = test_protection_single_r00_rw0,
		.protection_single_0w0_0w0 = test_protection_single_0w0_0w0,
		.protection_single_0w0_rw0 = test_protection_single_0w0_rw0,
		.protection_single_rw0_rw0 = test_protection_single_rw0_rw0,

		.protection_pairs_000_000 = test_protection_pair_000_000,
		.protection_pairs_000_r00 = test_protection_pair_000_r00,
		.protection_pairs_000_0w0 = test_protection_pair_000_0w0,
		.protection_pairs_000_rw0 = test_protection_pair_000_rw0,
		.protection_pairs_r00_000 = test_protection_pair_r00_000,
		.protection_pairs_r00_r00 = test_protection_pair_r00_r00,
		.protection_pairs_r00_0w0 = test_protection_pair_r00_0w0,
		.protection_pairs_r00_rw0 = test_protection_pair_r00_rw0,
		.protection_pairs_0w0_000 = test_protection_pair_0w0_000,
		.protection_pairs_0w0_r00 = test_protection_pair_0w0_r00,
		.protection_pairs_0w0_0w0 = test_protection_pair_0w0_0w0,
		.protection_pairs_0w0_rw0 = test_protection_pair_0w0_rw0,
		.protection_pairs_rw0_000 = test_protection_pair_rw0_000,
		.protection_pairs_rw0_r00 = test_protection_pair_rw0_r00,
		.protection_pairs_rw0_0w0 = test_protection_pair_rw0_0w0,
		.protection_pairs_rw0_rw0 = test_protection_pair_rw0_rw0,
	};

	run_vm_tests("test_vm_configurator", __FILE__, &tests, argc, argv);
}
