// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <tuple>
#include "build/build_config.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_tree.h"
// The purpose of this script is to fuzz code that parses
// table-like structures. As a result, we want to generate
// accessibility trees that contain lots of table-related
// roles.
//
// We also bias towards cells and rows so that we end up
// with more of those overall.
ax::mojom::Role GetInterestingTableRole(unsigned char byte) {
switch (byte % 16) {
default:
case 0:
case 1:
case 2:
case 3:
return ax::mojom::Role::kCell;
case 4:
case 5:
return ax::mojom::Role::kRow;
case 6:
return ax::mojom::Role::kTable;
case 7:
return ax::mojom::Role::kGrid;
case 8:
return ax::mojom::Role::kColumnHeader;
case 9:
return ax::mojom::Role::kRowHeader;
case 10:
return ax::mojom::Role::kGenericContainer;
case 11:
return ax::mojom::Role::kNone;
case 12:
return ax::mojom::Role::kLayoutTable;
case 13:
return ax::mojom::Role::kLayoutTableCell;
case 14:
return ax::mojom::Role::kLayoutTableRow;
case 15:
return ax::mojom::Role::kMain;
}
}
// We want some of the nodes in the accessibility tree to have
// table-related attributes.
ax::mojom::IntAttribute GetInterestingTableAttribute(unsigned char byte) {
switch (byte % 10) {
case 0:
default:
return ax::mojom::IntAttribute::kTableCellRowIndex;
case 1:
return ax::mojom::IntAttribute::kTableCellColumnIndex;
case 2:
return ax::mojom::IntAttribute::kTableRowCount;
case 3:
return ax::mojom::IntAttribute::kTableColumnCount;
case 4:
return ax::mojom::IntAttribute::kAriaRowCount;
case 5:
return ax::mojom::IntAttribute::kAriaColumnCount;
case 6:
return ax::mojom::IntAttribute::kTableCellRowSpan;
case 7:
return ax::mojom::IntAttribute::kTableCellColumnSpan;
case 8:
return ax::mojom::IntAttribute::kAriaCellRowIndex;
case 9:
return ax::mojom::IntAttribute::kAriaCellColumnIndex;
}
}
// Call all of the table-related APIs on an accessibility node.
// These will be no-ops if the node is not part of a complete
// table. We don't care about any of the results, we just want
// to make sure none of these crash or hang.
void TestTableAPIs(const ui::AXNode* node) {
std::ignore = node->IsTable();
std::ignore = node->GetTableColCount();
std::ignore = node->GetTableRowCount();
std::ignore = node->GetTableAriaColCount();
std::ignore = node->GetTableAriaRowCount();
std::ignore = node->GetTableCellCount();
std::ignore = node->GetTableCaption();
for (int i = 0; i < 8; i++)
std::ignore = node->GetTableCellFromIndex(i);
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
std::ignore = node->GetTableCellFromCoords(i, j);
// Note: some of the APIs return IDs - we don't care what's
// returned, we just want to make sure these APIs don't
// crash. Normally |ids| is an out argument only, but
// there's no reason we shouldn't be able to pass a vector
// that was previously used by another call.
std::vector<ui::AXNodeID> ids;
for (int i = 0; i < 3; i++) {
std::vector<ui::AXNodeID> col_header_node_ids =
node->GetTableColHeaderNodeIds(i);
ids.insert(ids.end(), col_header_node_ids.begin(),
col_header_node_ids.end());
std::vector<ui::AXNodeID> row_header_node_ids =
node->GetTableRowHeaderNodeIds(i);
ids.insert(ids.end(), row_header_node_ids.begin(),
row_header_node_ids.end());
}
std::vector<ui::AXNodeID> unique_cell_ids = node->GetTableUniqueCellIds();
ids.insert(ids.end(), unique_cell_ids.begin(), unique_cell_ids.end());
std::ignore = node->IsTableRow();
std::ignore = node->GetTableRowRowIndex();
#if BUILDFLAG(IS_APPLE)
std::ignore = node->IsTableColumn();
std::ignore = node->GetTableColColIndex();
#endif
std::ignore = node->IsTableCellOrHeader();
std::ignore = node->GetTableCellIndex();
std::ignore = node->GetTableCellColIndex();
std::ignore = node->GetTableCellRowIndex();
std::ignore = node->GetTableCellColSpan();
std::ignore = node->GetTableCellRowSpan();
std::ignore = node->GetTableCellAriaColIndex();
std::ignore = node->GetTableCellAriaRowIndex();
std::vector<ui::AXNodeID> cell_col_header_node_ids =
node->GetTableCellColHeaderNodeIds();
ids.insert(ids.end(), cell_col_header_node_ids.begin(),
cell_col_header_node_ids.end());
std::vector<ui::AXNodeID> cell_row_header_node_ids =
node->GetTableCellRowHeaderNodeIds();
ids.insert(ids.end(), cell_row_header_node_ids.begin(),
cell_row_header_node_ids.end());
std::vector<ui::AXNode*> headers;
node->GetTableCellColHeaders(&headers);
node->GetTableCellRowHeaders(&headers);
for (const ui::AXNode* child : node->children()) {
TestTableAPIs(child);
}
}
// Entry point for LibFuzzer.
extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) {
ui::AXTreeUpdate initial_state;
initial_state.root_id = 1;
size_t i = 0;
// The root of the accessibility tree.
ui::AXNodeData root;
root.id = 1;
if (i < size)
root.role = GetInterestingTableRole(data[i++]);
root.child_ids.push_back(2);
initial_state.nodes.push_back(root);
// Force the next node of the accessibility tree to be a table,
// and give it no attributes but a few children.
ui::AXNodeData table;
table.id = 2;
table.role = ax::mojom::Role::kTable;
if (i < size) {
size_t child_count = data[i++] % 8;
for (size_t j = 0; j < child_count && i < size; j++)
table.child_ids.push_back(3 + data[i++] % 32);
}
initial_state.nodes.push_back(table);
// Create more accessibility nodes that might result in a table.
int next_id = 3;
while (i < size) {
ui::AXNodeData node;
node.id = next_id++;
if (i < size)
node.role = GetInterestingTableRole(data[i++]);
if (i < size) {
int attr_count = data[i++] % 6;
for (int j = 0; j < attr_count && i + 1 < size; j++) {
unsigned char attr = data[i++];
int32_t value = static_cast<int32_t>(data[i++]) - 2;
node.AddIntAttribute(GetInterestingTableAttribute(attr), value);
}
}
if (i < size) {
size_t child_count = data[i++] % 8;
for (size_t j = 0; j < child_count && i < size; j++)
node.child_ids.push_back(4 + data[i++] % 32);
}
initial_state.nodes.push_back(node);
}
// Run with --v=1 to aid in debugging a specific crash.
VLOG(1) << "Input accessibility tree:\n" << initial_state.ToString();
ui::AXTree tree;
if (tree.Unserialize(initial_state))
TestTableAPIs(tree.root());
return 0;
}