blob: 107d0ca8c0767e885fbfa5fd6380d9246bf39ccf [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/macros.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::kIgnored;
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(ui::AXNode* node) {
ignore_result(node->IsTable());
ignore_result(node->GetTableColCount());
ignore_result(node->GetTableRowCount());
ignore_result(node->GetTableAriaColCount());
ignore_result(node->GetTableAriaRowCount());
ignore_result(node->GetTableCellCount());
for (int i = 0; i < 8; i++)
ignore_result(node->GetTableCellFromIndex(i));
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
ignore_result(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<int32_t> ids;
for (int i = 0; i < 3; i++) {
node->GetTableColHeaderNodeIds(i, &ids);
node->GetTableRowHeaderNodeIds(i, &ids);
}
node->GetTableUniqueCellIds(&ids);
ignore_result(node->IsTableRow());
ignore_result(node->GetTableRowRowIndex());
ignore_result(node->IsTableCellOrHeader());
ignore_result(node->GetTableCellIndex());
ignore_result(node->GetTableCellColIndex());
ignore_result(node->GetTableCellRowIndex());
ignore_result(node->GetTableCellColSpan());
ignore_result(node->GetTableCellRowSpan());
ignore_result(node->GetTableCellAriaColIndex());
ignore_result(node->GetTableCellAriaRowIndex());
node->GetTableCellColHeaderNodeIds(&ids);
node->GetTableCellRowHeaderNodeIds(&ids);
std::vector<ui::AXNode*> headers;
node->GetTableCellColHeaders(&headers);
node->GetTableCellRowHeaders(&headers);
for (int i = 0; i < node->child_count(); i++)
TestTableAPIs(node->children()[i]);
}
// 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;
}