|  | // 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/replayer.h" | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "source/fuzz/data_descriptor.h" | 
|  | #include "source/fuzz/fuzzer_util.h" | 
|  | #include "source/fuzz/instruction_descriptor.h" | 
|  | #include "source/fuzz/transformation_add_constant_scalar.h" | 
|  | #include "source/fuzz/transformation_add_global_variable.h" | 
|  | #include "source/fuzz/transformation_add_parameter.h" | 
|  | #include "source/fuzz/transformation_add_synonym.h" | 
|  | #include "source/fuzz/transformation_flatten_conditional_branch.h" | 
|  | #include "source/fuzz/transformation_split_block.h" | 
|  | #include "test/fuzz/fuzz_test_util.h" | 
|  |  | 
|  | namespace spvtools { | 
|  | namespace fuzz { | 
|  | namespace { | 
|  |  | 
|  | TEST(ReplayerTest, PartialReplay) { | 
|  | const std::string kTestShader = R"( | 
|  | OpCapability Shader | 
|  | %1 = OpExtInstImport "GLSL.std.450" | 
|  | OpMemoryModel Logical GLSL450 | 
|  | OpEntryPoint Fragment %4 "main" | 
|  | OpExecutionMode %4 OriginUpperLeft | 
|  | OpSource ESSL 310 | 
|  | OpName %4 "main" | 
|  | OpName %8 "g" | 
|  | OpName %11 "x" | 
|  | %2 = OpTypeVoid | 
|  | %3 = OpTypeFunction %2 | 
|  | %6 = OpTypeInt 32 1 | 
|  | %7 = OpTypePointer Private %6 | 
|  | %8 = OpVariable %7 Private | 
|  | %9 = OpConstant %6 10 | 
|  | %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 | 
|  | %13 = OpLoad %6 %8 | 
|  | OpStore %11 %13 | 
|  | %14 = OpLoad %6 %8 | 
|  | OpStore %11 %14 | 
|  | %15 = OpLoad %6 %8 | 
|  | OpStore %11 %15 | 
|  | %16 = OpLoad %6 %8 | 
|  | OpStore %11 %16 | 
|  | %17 = OpLoad %6 %8 | 
|  | OpStore %11 %17 | 
|  | %18 = OpLoad %6 %8 | 
|  | OpStore %11 %18 | 
|  | %19 = OpLoad %6 %8 | 
|  | OpStore %11 %19 | 
|  | %20 = OpLoad %6 %8 | 
|  | OpStore %11 %20 | 
|  | %21 = OpLoad %6 %8 | 
|  | OpStore %11 %21 | 
|  | %22 = OpLoad %6 %8 | 
|  | OpStore %11 %22 | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | )"; | 
|  |  | 
|  | const auto env = SPV_ENV_UNIVERSAL_1_3; | 
|  | spvtools::ValidatorOptions validator_options; | 
|  |  | 
|  | std::vector<uint32_t> binary_in; | 
|  | SpirvTools t(env); | 
|  | t.SetMessageConsumer(kConsoleMessageConsumer); | 
|  | ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption)); | 
|  | ASSERT_TRUE(t.Validate(binary_in)); | 
|  |  | 
|  | protobufs::TransformationSequence transformations; | 
|  | for (uint32_t id = 12; id <= 22; id++) { | 
|  | *transformations.add_transformation() = | 
|  | TransformationSplitBlock(MakeInstructionDescriptor(id, SpvOpLoad, 0), | 
|  | id + 100) | 
|  | .ToMessage(); | 
|  | } | 
|  |  | 
|  | { | 
|  | // Full replay | 
|  | protobufs::FactSequence empty_facts; | 
|  | auto replayer_result = | 
|  | Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts, | 
|  | transformations, 11, true, validator_options) | 
|  | .Run(); | 
|  | // Replay should succeed. | 
|  | ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, | 
|  | replayer_result.status); | 
|  | // All transformations should be applied. | 
|  | ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals( | 
|  | transformations, replayer_result.applied_transformations)); | 
|  |  | 
|  | const std::string kFullySplitShader = R"( | 
|  | OpCapability Shader | 
|  | %1 = OpExtInstImport "GLSL.std.450" | 
|  | OpMemoryModel Logical GLSL450 | 
|  | OpEntryPoint Fragment %4 "main" | 
|  | OpExecutionMode %4 OriginUpperLeft | 
|  | OpSource ESSL 310 | 
|  | OpName %4 "main" | 
|  | OpName %8 "g" | 
|  | OpName %11 "x" | 
|  | %2 = OpTypeVoid | 
|  | %3 = OpTypeFunction %2 | 
|  | %6 = OpTypeInt 32 1 | 
|  | %7 = OpTypePointer Private %6 | 
|  | %8 = OpVariable %7 Private | 
|  | %9 = OpConstant %6 10 | 
|  | %10 = OpTypePointer Function %6 | 
|  | %4 = OpFunction %2 None %3 | 
|  | %5 = OpLabel | 
|  | %11 = OpVariable %10 Function | 
|  | OpStore %8 %9 | 
|  | OpBranch %112 | 
|  | %112 = OpLabel | 
|  | %12 = OpLoad %6 %8 | 
|  | OpStore %11 %12 | 
|  | OpBranch %113 | 
|  | %113 = OpLabel | 
|  | %13 = OpLoad %6 %8 | 
|  | OpStore %11 %13 | 
|  | OpBranch %114 | 
|  | %114 = OpLabel | 
|  | %14 = OpLoad %6 %8 | 
|  | OpStore %11 %14 | 
|  | OpBranch %115 | 
|  | %115 = OpLabel | 
|  | %15 = OpLoad %6 %8 | 
|  | OpStore %11 %15 | 
|  | OpBranch %116 | 
|  | %116 = OpLabel | 
|  | %16 = OpLoad %6 %8 | 
|  | OpStore %11 %16 | 
|  | OpBranch %117 | 
|  | %117 = OpLabel | 
|  | %17 = OpLoad %6 %8 | 
|  | OpStore %11 %17 | 
|  | OpBranch %118 | 
|  | %118 = OpLabel | 
|  | %18 = OpLoad %6 %8 | 
|  | OpStore %11 %18 | 
|  | OpBranch %119 | 
|  | %119 = OpLabel | 
|  | %19 = OpLoad %6 %8 | 
|  | OpStore %11 %19 | 
|  | OpBranch %120 | 
|  | %120 = OpLabel | 
|  | %20 = OpLoad %6 %8 | 
|  | OpStore %11 %20 | 
|  | OpBranch %121 | 
|  | %121 = OpLabel | 
|  | %21 = OpLoad %6 %8 | 
|  | OpStore %11 %21 | 
|  | OpBranch %122 | 
|  | %122 = OpLabel | 
|  | %22 = OpLoad %6 %8 | 
|  | OpStore %11 %22 | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | )"; | 
|  | ASSERT_TRUE(IsEqual(env, kFullySplitShader, | 
|  | replayer_result.transformed_module.get())); | 
|  | } | 
|  |  | 
|  | { | 
|  | // Half replay | 
|  | protobufs::FactSequence empty_facts; | 
|  | auto replayer_result = | 
|  | Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts, | 
|  | transformations, 5, true, validator_options) | 
|  | .Run(); | 
|  | // Replay should succeed. | 
|  | ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, | 
|  | replayer_result.status); | 
|  | // The first 5 transformations should be applied | 
|  | ASSERT_EQ(5, replayer_result.applied_transformations.transformation_size()); | 
|  | for (uint32_t i = 0; i < 5; i++) { | 
|  | ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals( | 
|  | transformations.transformation(i), | 
|  | replayer_result.applied_transformations.transformation(i))); | 
|  | } | 
|  |  | 
|  | const std::string kHalfSplitShader = R"( | 
|  | OpCapability Shader | 
|  | %1 = OpExtInstImport "GLSL.std.450" | 
|  | OpMemoryModel Logical GLSL450 | 
|  | OpEntryPoint Fragment %4 "main" | 
|  | OpExecutionMode %4 OriginUpperLeft | 
|  | OpSource ESSL 310 | 
|  | OpName %4 "main" | 
|  | OpName %8 "g" | 
|  | OpName %11 "x" | 
|  | %2 = OpTypeVoid | 
|  | %3 = OpTypeFunction %2 | 
|  | %6 = OpTypeInt 32 1 | 
|  | %7 = OpTypePointer Private %6 | 
|  | %8 = OpVariable %7 Private | 
|  | %9 = OpConstant %6 10 | 
|  | %10 = OpTypePointer Function %6 | 
|  | %4 = OpFunction %2 None %3 | 
|  | %5 = OpLabel | 
|  | %11 = OpVariable %10 Function | 
|  | OpStore %8 %9 | 
|  | OpBranch %112 | 
|  | %112 = OpLabel | 
|  | %12 = OpLoad %6 %8 | 
|  | OpStore %11 %12 | 
|  | OpBranch %113 | 
|  | %113 = OpLabel | 
|  | %13 = OpLoad %6 %8 | 
|  | OpStore %11 %13 | 
|  | OpBranch %114 | 
|  | %114 = OpLabel | 
|  | %14 = OpLoad %6 %8 | 
|  | OpStore %11 %14 | 
|  | OpBranch %115 | 
|  | %115 = OpLabel | 
|  | %15 = OpLoad %6 %8 | 
|  | OpStore %11 %15 | 
|  | OpBranch %116 | 
|  | %116 = OpLabel | 
|  | %16 = OpLoad %6 %8 | 
|  | OpStore %11 %16 | 
|  | %17 = OpLoad %6 %8 | 
|  | OpStore %11 %17 | 
|  | %18 = OpLoad %6 %8 | 
|  | OpStore %11 %18 | 
|  | %19 = OpLoad %6 %8 | 
|  | OpStore %11 %19 | 
|  | %20 = OpLoad %6 %8 | 
|  | OpStore %11 %20 | 
|  | %21 = OpLoad %6 %8 | 
|  | OpStore %11 %21 | 
|  | %22 = OpLoad %6 %8 | 
|  | OpStore %11 %22 | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | )"; | 
|  | ASSERT_TRUE(IsEqual(env, kHalfSplitShader, | 
|  | replayer_result.transformed_module.get())); | 
|  | } | 
|  |  | 
|  | { | 
|  | // Empty replay | 
|  | protobufs::FactSequence empty_facts; | 
|  | auto replayer_result = | 
|  | Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts, | 
|  | transformations, 0, true, validator_options) | 
|  | .Run(); | 
|  | // Replay should succeed. | 
|  | ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, | 
|  | replayer_result.status); | 
|  | // No transformations should be applied | 
|  | ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size()); | 
|  | ASSERT_TRUE( | 
|  | IsEqual(env, kTestShader, replayer_result.transformed_module.get())); | 
|  | } | 
|  |  | 
|  | { | 
|  | // Invalid replay: too many transformations | 
|  | protobufs::FactSequence empty_facts; | 
|  | // The number of transformations requested to be applied exceeds the number | 
|  | // of transformations | 
|  | auto replayer_result = | 
|  | Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts, | 
|  | transformations, 12, true, validator_options) | 
|  | .Run(); | 
|  |  | 
|  | // Replay should not succeed. | 
|  | ASSERT_EQ(Replayer::ReplayerResultStatus::kTooManyTransformationsRequested, | 
|  | replayer_result.status); | 
|  | // No transformations should be applied | 
|  | ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size()); | 
|  | // The output binary should be empty | 
|  | ASSERT_EQ(nullptr, replayer_result.transformed_module); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(ReplayerTest, CheckFactsAfterReplay) { | 
|  | const std::string kTestShader = 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 | 
|  | %8 = OpTypeInt 32 1 | 
|  | %9 = OpTypePointer Function %8 | 
|  | %50 = OpTypePointer Private %8 | 
|  | %11 = OpConstant %8 1 | 
|  | %4 = OpFunction %2 None %3 | 
|  | %5 = OpLabel | 
|  | %10 = OpVariable %9 Function | 
|  | OpStore %10 %11 | 
|  | %12 = OpFunctionCall %2 %6 | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | %6 = OpFunction %2 None %3 | 
|  | %7 = OpLabel | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | )"; | 
|  |  | 
|  | const auto env = SPV_ENV_UNIVERSAL_1_3; | 
|  | spvtools::ValidatorOptions validator_options; | 
|  |  | 
|  | std::vector<uint32_t> binary_in; | 
|  | SpirvTools t(env); | 
|  | t.SetMessageConsumer(kConsoleMessageConsumer); | 
|  | ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption)); | 
|  | ASSERT_TRUE(t.Validate(binary_in)); | 
|  |  | 
|  | protobufs::TransformationSequence transformations; | 
|  | *transformations.add_transformation() = | 
|  | TransformationAddConstantScalar(100, 8, {42}, true).ToMessage(); | 
|  | *transformations.add_transformation() = | 
|  | TransformationAddGlobalVariable(101, 50, SpvStorageClassPrivate, 100, | 
|  | true) | 
|  | .ToMessage(); | 
|  | *transformations.add_transformation() = | 
|  | TransformationAddParameter(6, 102, 8, {{12, 100}}, 103).ToMessage(); | 
|  | *transformations.add_transformation() = | 
|  | TransformationAddSynonym( | 
|  | 11, | 
|  | protobufs::TransformationAddSynonym::SynonymType:: | 
|  | TransformationAddSynonym_SynonymType_COPY_OBJECT, | 
|  | 104, MakeInstructionDescriptor(12, SpvOpFunctionCall, 0)) | 
|  | .ToMessage(); | 
|  |  | 
|  | // Full replay | 
|  | protobufs::FactSequence empty_facts; | 
|  | auto replayer_result = | 
|  | Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts, | 
|  | transformations, transformations.transformation_size(), true, | 
|  | validator_options) | 
|  | .Run(); | 
|  | // Replay should succeed. | 
|  | ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, replayer_result.status); | 
|  | // All transformations should be applied. | 
|  | ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals( | 
|  | transformations, replayer_result.applied_transformations)); | 
|  |  | 
|  | const std::string kExpected = 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 | 
|  | %8 = OpTypeInt 32 1 | 
|  | %9 = OpTypePointer Function %8 | 
|  | %50 = OpTypePointer Private %8 | 
|  | %11 = OpConstant %8 1 | 
|  | %100 = OpConstant %8 42 | 
|  | %101 = OpVariable %50 Private %100 | 
|  | %103 = OpTypeFunction %2 %8 | 
|  | %4 = OpFunction %2 None %3 | 
|  | %5 = OpLabel | 
|  | %10 = OpVariable %9 Function | 
|  | OpStore %10 %11 | 
|  | %104 = OpCopyObject %8 %11 | 
|  | %12 = OpFunctionCall %2 %6 %100 | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | %6 = OpFunction %2 None %103 | 
|  | %102 = OpFunctionParameter %8 | 
|  | %7 = OpLabel | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | )"; | 
|  | ASSERT_TRUE( | 
|  | IsEqual(env, kExpected, replayer_result.transformed_module.get())); | 
|  |  | 
|  | ASSERT_TRUE( | 
|  | replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant( | 
|  | 100)); | 
|  | ASSERT_TRUE(replayer_result.transformation_context->GetFactManager() | 
|  | ->PointeeValueIsIrrelevant(101)); | 
|  | ASSERT_TRUE( | 
|  | replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant( | 
|  | 102)); | 
|  | ASSERT_TRUE( | 
|  | replayer_result.transformation_context->GetFactManager()->IsSynonymous( | 
|  | MakeDataDescriptor(11, {}), MakeDataDescriptor(104, {}))); | 
|  | } | 
|  |  | 
|  | TEST(ReplayerTest, ReplayWithOverflowIds) { | 
|  | const std::string kTestShader = 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 | 
|  | %50 = OpTypePointer Private %6 | 
|  | %9 = OpConstant %6 2 | 
|  | %11 = OpConstant %6 0 | 
|  | %12 = OpTypeBool | 
|  | %17 = OpConstant %6 1 | 
|  | %4 = OpFunction %2 None %3 | 
|  | %5 = OpLabel | 
|  | %8 = OpVariable %7 Function | 
|  | OpStore %8 %9 | 
|  | %10 = OpLoad %6 %8 | 
|  | %13 = OpSGreaterThan %12 %10 %11 | 
|  | OpSelectionMerge %15 None | 
|  | OpBranchConditional %13 %14 %15 | 
|  | %14 = OpLabel | 
|  | %16 = OpLoad %6 %8 | 
|  | %18 = OpIAdd %6 %16 %17 | 
|  | OpStore %8 %18 | 
|  | OpBranch %15 | 
|  | %15 = OpLabel | 
|  | OpReturn | 
|  | OpFunctionEnd | 
|  | )"; | 
|  |  | 
|  | const auto env = SPV_ENV_UNIVERSAL_1_3; | 
|  | spvtools::ValidatorOptions validator_options; | 
|  |  | 
|  | std::vector<uint32_t> binary_in; | 
|  | SpirvTools t(env); | 
|  | t.SetMessageConsumer(kConsoleMessageConsumer); | 
|  | ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption)); | 
|  | ASSERT_TRUE(t.Validate(binary_in)); | 
|  |  | 
|  | protobufs::TransformationSequence transformations; | 
|  | *transformations.add_transformation() = | 
|  | TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {}).ToMessage(); | 
|  | *transformations.add_transformation() = | 
|  | TransformationAddGlobalVariable(101, 50, SpvStorageClassPrivate, 11, true) | 
|  | .ToMessage(); | 
|  |  | 
|  | protobufs::FactSequence empty_facts; | 
|  | auto replayer_result = | 
|  | Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts, | 
|  | transformations, transformations.transformation_size(), true, | 
|  | validator_options) | 
|  | .Run(); | 
|  | // Replay should succeed. | 
|  | ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, replayer_result.status); | 
|  | // All transformations should be applied. | 
|  | ASSERT_EQ(2, replayer_result.applied_transformations.transformation_size()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace fuzz | 
|  | }  // namespace spvtools |