| // Copyright (c) 2020 Google LLC | 
 | // | 
 | // 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 "source/fuzz/shrinker.h" | 
 |  | 
 | #include "gtest/gtest.h" | 
 | #include "source/fuzz/fact_manager/fact_manager.h" | 
 | #include "source/fuzz/fuzzer_context.h" | 
 | #include "source/fuzz/fuzzer_pass_donate_modules.h" | 
 | #include "source/fuzz/fuzzer_util.h" | 
 | #include "source/fuzz/pseudo_random_generator.h" | 
 | #include "source/fuzz/transformation_context.h" | 
 | #include "source/opt/ir_context.h" | 
 | #include "source/util/make_unique.h" | 
 | #include "test/fuzz/fuzz_test_util.h" | 
 |  | 
 | namespace spvtools { | 
 | namespace fuzz { | 
 | namespace { | 
 |  | 
 | TEST(ShrinkerTest, ReduceAddedFunctions) { | 
 |   const std::string kReferenceModule = R"( | 
 |                OpCapability Shader | 
 |           %1 = OpExtInstImport "GLSL.std.450" | 
 |                OpMemoryModel Logical GLSL450 | 
 |                OpEntryPoint Fragment %4 "main" | 
 |                OpExecutionMode %4 OriginUpperLeft | 
 |                OpSource ESSL 320 | 
 |           %2 = OpTypeVoid | 
 |           %3 = OpTypeFunction %2 | 
 |           %6 = OpTypeInt 32 1 | 
 |           %7 = OpTypePointer Private %6 | 
 |           %8 = OpVariable %7 Private | 
 |           %9 = OpConstant %6 2 | 
 |          %10 = OpTypePointer Function %6 | 
 |           %4 = OpFunction %2 None %3 | 
 |           %5 = OpLabel | 
 |          %11 = OpVariable %10 Function | 
 |                OpStore %8 %9 | 
 |          %12 = OpLoad %6 %8 | 
 |                OpStore %11 %12 | 
 |                OpReturn | 
 |                OpFunctionEnd | 
 |   )"; | 
 |  | 
 |   const std::string kDonorModule = R"( | 
 |                OpCapability Shader | 
 |           %1 = OpExtInstImport "GLSL.std.450" | 
 |                OpMemoryModel Logical GLSL450 | 
 |                OpEntryPoint Fragment %4 "main" | 
 |                OpExecutionMode %4 OriginUpperLeft | 
 |                OpSource ESSL 320 | 
 |           %2 = OpTypeVoid | 
 |           %3 = OpTypeFunction %2 | 
 |           %6 = OpTypeInt 32 1 | 
 |           %7 = OpTypePointer Function %6 | 
 |           %8 = OpTypeFunction %6 %7 | 
 |          %12 = OpTypeFunction %2 %7 | 
 |          %17 = OpConstant %6 0 | 
 |          %26 = OpTypeBool | 
 |          %32 = OpConstant %6 1 | 
 |          %46 = OpTypePointer Private %6 | 
 |          %47 = OpVariable %46 Private | 
 |          %48 = OpConstant %6 3 | 
 |           %4 = OpFunction %2 None %3 | 
 |           %5 = OpLabel | 
 |          %49 = OpVariable %7 Function | 
 |          %50 = OpVariable %7 Function | 
 |          %51 = OpLoad %6 %49 | 
 |                OpStore %50 %51 | 
 |          %52 = OpFunctionCall %2 %14 %50 | 
 |                OpReturn | 
 |                OpFunctionEnd | 
 |          %10 = OpFunction %6 None %8 | 
 |           %9 = OpFunctionParameter %7 | 
 |          %11 = OpLabel | 
 |          %16 = OpVariable %7 Function | 
 |          %18 = OpVariable %7 Function | 
 |                OpStore %16 %17 | 
 |                OpStore %18 %17 | 
 |                OpBranch %19 | 
 |          %19 = OpLabel | 
 |                OpLoopMerge %21 %22 None | 
 |                OpBranch %23 | 
 |          %23 = OpLabel | 
 |          %24 = OpLoad %6 %18 | 
 |          %25 = OpLoad %6 %9 | 
 |          %27 = OpSLessThan %26 %24 %25 | 
 |                OpBranchConditional %27 %20 %21 | 
 |          %20 = OpLabel | 
 |          %28 = OpLoad %6 %9 | 
 |          %29 = OpLoad %6 %16 | 
 |          %30 = OpIAdd %6 %29 %28 | 
 |                OpStore %16 %30 | 
 |                OpBranch %22 | 
 |          %22 = OpLabel | 
 |          %31 = OpLoad %6 %18 | 
 |          %33 = OpIAdd %6 %31 %32 | 
 |                OpStore %18 %33 | 
 |                OpBranch %19 | 
 |          %21 = OpLabel | 
 |          %34 = OpLoad %6 %16 | 
 |          %35 = OpNot %6 %34 | 
 |                OpReturnValue %35 | 
 |                OpFunctionEnd | 
 |          %14 = OpFunction %2 None %12 | 
 |          %13 = OpFunctionParameter %7 | 
 |          %15 = OpLabel | 
 |          %37 = OpVariable %7 Function | 
 |          %38 = OpVariable %7 Function | 
 |          %39 = OpLoad %6 %13 | 
 |                OpStore %38 %39 | 
 |          %40 = OpFunctionCall %6 %10 %38 | 
 |                OpStore %37 %40 | 
 |          %41 = OpLoad %6 %37 | 
 |          %42 = OpLoad %6 %13 | 
 |          %43 = OpSGreaterThan %26 %41 %42 | 
 |                OpSelectionMerge %45 None | 
 |                OpBranchConditional %43 %44 %45 | 
 |          %44 = OpLabel | 
 |                OpStore %47 %48 | 
 |                OpBranch %45 | 
 |          %45 = OpLabel | 
 |                OpReturn | 
 |                OpFunctionEnd | 
 |   )"; | 
 |  | 
 |   // Note: |env| should ideally be declared const.  However, due to a known | 
 |   // issue with older versions of MSVC we would have to mark |env| as being | 
 |   // captured due to its used in a lambda below, and other compilers would warn | 
 |   // that such capturing is not necessary.  Not declaring |env| as const means | 
 |   // that it needs to be captured to be used in the lambda, and thus all | 
 |   // compilers are kept happy.  See: | 
 |   // https://developercommunity.visualstudio.com/content/problem/367326/problems-with-capturing-constexpr-in-lambda.html | 
 |   spv_target_env env = SPV_ENV_UNIVERSAL_1_3; | 
 |   const auto consumer = kConsoleMessageConsumer; | 
 |  | 
 |   SpirvTools tools(env); | 
 |   std::vector<uint32_t> reference_binary; | 
 |   ASSERT_TRUE( | 
 |       tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption)); | 
 |  | 
 |   spvtools::ValidatorOptions validator_options; | 
 |  | 
 |   const auto variant_ir_context = | 
 |       BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption); | 
 |   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( | 
 |       variant_ir_context.get(), validator_options, kConsoleMessageConsumer)); | 
 |  | 
 |   const auto donor_ir_context = | 
 |       BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption); | 
 |   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( | 
 |       donor_ir_context.get(), validator_options, kConsoleMessageConsumer)); | 
 |  | 
 |   PseudoRandomGenerator random_generator(0); | 
 |   FuzzerContext fuzzer_context(&random_generator, 100); | 
 |   TransformationContext transformation_context( | 
 |       MakeUnique<FactManager>(variant_ir_context.get()), validator_options); | 
 |  | 
 |   protobufs::TransformationSequence transformations; | 
 |   FuzzerPassDonateModules pass(variant_ir_context.get(), | 
 |                                &transformation_context, &fuzzer_context, | 
 |                                &transformations, {}); | 
 |   pass.DonateSingleModule(donor_ir_context.get(), true); | 
 |  | 
 |   protobufs::FactSequence no_facts; | 
 |  | 
 |   Shrinker::InterestingnessFunction interestingness_function = | 
 |       [consumer, env](const std::vector<uint32_t>& binary, | 
 |                       uint32_t /*unused*/) -> bool { | 
 |     bool found_op_not = false; | 
 |     uint32_t op_call_count = 0; | 
 |     auto temp_ir_context = | 
 |         BuildModule(env, consumer, binary.data(), binary.size()); | 
 |     for (auto& function : *temp_ir_context->module()) { | 
 |       for (auto& block : function) { | 
 |         for (auto& inst : block) { | 
 |           if (inst.opcode() == SpvOpNot) { | 
 |             found_op_not = true; | 
 |           } else if (inst.opcode() == SpvOpFunctionCall) { | 
 |             op_call_count++; | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |     return found_op_not && op_call_count >= 2; | 
 |   }; | 
 |  | 
 |   auto shrinker_result = | 
 |       Shrinker(env, consumer, reference_binary, no_facts, transformations, | 
 |                interestingness_function, 1000, true, validator_options) | 
 |           .Run(); | 
 |   ASSERT_EQ(Shrinker::ShrinkerResultStatus::kComplete, shrinker_result.status); | 
 |  | 
 |   // We now check that the module after shrinking looks right. | 
 |   // The entry point should be identical to what it looked like in the | 
 |   // reference, while the other functions should be absolutely minimal, | 
 |   // containing only what is needed to satisfy the interestingness function. | 
 |   auto ir_context_after_shrinking = | 
 |       BuildModule(env, consumer, shrinker_result.transformed_binary.data(), | 
 |                   shrinker_result.transformed_binary.size()); | 
 |   bool first_function = true; | 
 |   for (auto& function : *ir_context_after_shrinking->module()) { | 
 |     if (first_function) { | 
 |       first_function = false; | 
 |       bool first_block = true; | 
 |       for (auto& block : function) { | 
 |         ASSERT_TRUE(first_block); | 
 |         uint32_t counter = 0; | 
 |         for (auto& inst : block) { | 
 |           switch (counter) { | 
 |             case 0: | 
 |               ASSERT_EQ(SpvOpVariable, inst.opcode()); | 
 |               ASSERT_EQ(11, inst.result_id()); | 
 |               break; | 
 |             case 1: | 
 |               ASSERT_EQ(SpvOpStore, inst.opcode()); | 
 |               break; | 
 |             case 2: | 
 |               ASSERT_EQ(SpvOpLoad, inst.opcode()); | 
 |               ASSERT_EQ(12, inst.result_id()); | 
 |               break; | 
 |             case 3: | 
 |               ASSERT_EQ(SpvOpStore, inst.opcode()); | 
 |               break; | 
 |             case 4: | 
 |               ASSERT_EQ(SpvOpReturn, inst.opcode()); | 
 |               break; | 
 |             default: | 
 |               FAIL(); | 
 |           } | 
 |           counter++; | 
 |         } | 
 |       } | 
 |     } else { | 
 |       bool first_block = true; | 
 |       for (auto& block : function) { | 
 |         ASSERT_TRUE(first_block); | 
 |         first_block = false; | 
 |         for (auto& inst : block) { | 
 |           switch (inst.opcode()) { | 
 |             case SpvOpVariable: | 
 |             case SpvOpNot: | 
 |             case SpvOpReturn: | 
 |             case SpvOpReturnValue: | 
 |             case SpvOpFunctionCall: | 
 |               // These are the only instructions we expect to see. | 
 |               break; | 
 |             default: | 
 |               FAIL(); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 | }  // namespace fuzz | 
 | }  // namespace spvtools |