// 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 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 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 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