// Copyright 2021 The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h" #include #include #include #include "src/tint/ast/block_statement.h" #include "src/tint/ast/for_loop_statement.h" #include "src/tint/ast/if_statement.h" #include "src/tint/ast/loop_statement.h" #include "src/tint/ast/variable_decl_statement.h" #include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h" #include "src/tint/fuzzers/tint_ast_fuzzer/util.h" #include "src/tint/program_builder.h" #include "src/tint/sem/for_loop_statement.h" #include "src/tint/sem/if_statement.h" #include "src/tint/sem/loop_statement.h" #include "src/tint/sem/statement.h" namespace tint::fuzzers::ast_fuzzer { MutationDeleteStatement::MutationDeleteStatement(protobufs::MutationDeleteStatement message) : message_(std::move(message)) {} MutationDeleteStatement::MutationDeleteStatement(uint32_t statement_id) { message_.set_statement_id(statement_id); } bool MutationDeleteStatement::IsApplicable(const tint::Program& program, const NodeIdMap& node_id_map) const { auto* statement_node = tint::As(node_id_map.GetNode(message_.statement_id())); if (!statement_node) { // The statement id is invalid or does not refer to a statement. return false; } const auto* statement_sem_node = tint::As(program.Sem().Get(statement_node)); if (!statement_sem_node) { // Semantic information for the statement is not available. This // information is required in order to perform the deletion. return false; } // Check whether it is OK to delete this statement. if (!CanBeDeleted(*statement_node, program, JumpTracker(program))) { return false; } return true; } void MutationDeleteStatement::Apply(const NodeIdMap& node_id_map, tint::CloneContext* clone_context, NodeIdMap* /* unused */) const { const auto* statement_node = tint::As(node_id_map.GetNode(message_.statement_id())); const auto* statement_sem_node = tint::As(clone_context->src->Sem().Get(statement_node)); const auto* sem_parent = statement_sem_node->Parent(); if (tint::Is(sem_parent) && tint::As(sem_parent->Declaration())->else_statement == statement_node) { // Remove the "else" part of an if statement. clone_context->Replace(statement_node, static_cast(nullptr)); } else if (tint::Is(sem_parent) && tint::As(sem_parent->Declaration())->initializer == statement_node) { // Remove the initializer of a for loop. clone_context->Replace(statement_node, static_cast(nullptr)); } else if (tint::Is(sem_parent) && tint::As(sem_parent->Declaration())->continuing == statement_node) { // Remove the "continuing" statement of a for loop. clone_context->Replace(statement_node, static_cast(nullptr)); } else if (tint::Is(statement_sem_node)) { // Remove the "continuing" block of a loop. clone_context->Replace(statement_node, static_cast(nullptr)); } else if (tint::Is(statement_node)) { // Remove a case statement from its enclosing switch statement. const auto& case_statement_list = &sem_parent->Declaration()->As()->body; assert(std::find(case_statement_list->begin(), case_statement_list->end(), statement_node) != case_statement_list->end() && "Statement not found."); clone_context->Remove(*case_statement_list, statement_node); } else if (tint::Is(statement_node)) { // Remove a block statement from the block that encloses it. A special case is required for // this, since a sem::Block has itself as its associated sem::Block, so it is necessary to // look at the parent to get the enclosing block. const auto& statement_list = sem_parent->Declaration()->As()->statements; assert(std::find(statement_list.begin(), statement_list.end(), statement_node) != statement_list.end() && "Statement not found."); clone_context->Remove(statement_list, statement_node); } else { // Remove a non-block statement from the block that encloses it. const auto& statement_list = statement_sem_node->Block()->Declaration()->As()->statements; assert(std::find(statement_list.begin(), statement_list.end(), statement_node) != statement_list.end() && "Statement not found."); clone_context->Remove(statement_list, statement_node); } } protobufs::Mutation MutationDeleteStatement::ToMessage() const { protobufs::Mutation mutation; *mutation.mutable_delete_statement() = message_; return mutation; } bool MutationDeleteStatement::CanBeDeleted(const ast::Statement& statement_node, const Program& program, const JumpTracker& jump_tracker) { if (statement_node.Is()) { // This is conservative. It would be possible to delete variable declarations if they are // not used. Further analysis could allow that. return false; } if (jump_tracker.ContainsReturn(statement_node) || jump_tracker.ContainsIntraproceduralDiscard(statement_node)) { // This is conservative. It would be possible to delete a return/discard statement as long // as there is still a return/discard on every control flow path. return false; } if (jump_tracker.ContainsBreakForInnermostLoop(statement_node)) { // This is conservative. Disallowing the removal of breaks ensures that loops cannot become // statically infinite. However, a loop might in practice have multiple breaks, some of // which can be removed. return false; } if (auto* case_statement = statement_node.As()) { // It is not OK to delete the case statement which contains the default selector. if (case_statement->ContainsDefault()) { return false; } } auto* parent_sem = program.Sem().Get(&statement_node)->Parent(); if (parent_sem == nullptr) { // Semantic information for the parent node is required. return false; } auto* parent_stmt = parent_sem->Declaration(); // It does not make sense to delete the entire body of a loop or if statement. if (auto* for_loop = parent_stmt->As()) { if (for_loop->body == &statement_node) { return false; } } if (auto* loop = parent_stmt->As()) { if (loop->body == &statement_node) { return false; } } if (auto* while_loop = parent_stmt->As()) { if (while_loop->body == &statement_node) { return false; } } if (auto* if_statement = parent_stmt->As()) { if (if_statement->body == &statement_node) { return false; } } return true; } } // namespace tint::fuzzers::ast_fuzzer