// 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. #include #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 ids; for (int i = 0; i < 3; i++) { std::vector col_header_node_ids = node->GetTableColHeaderNodeIds(i); ids.insert(ids.end(), col_header_node_ids.begin(), col_header_node_ids.end()); std::vector row_header_node_ids = node->GetTableRowHeaderNodeIds(i); ids.insert(ids.end(), row_header_node_ids.begin(), row_header_node_ids.end()); } std::vector 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 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 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 headers; node->GetTableCellColHeaders(&headers); node->GetTableCellRowHeaders(&headers); for (const auto* 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(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; }