| // 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/transformation_function_call.h" |
| |
| #include "gtest/gtest.h" |
| #include "source/fuzz/fuzzer_util.h" |
| #include "source/fuzz/instruction_descriptor.h" |
| #include "test/fuzz/fuzz_test_util.h" |
| |
| namespace spvtools { |
| namespace fuzz { |
| namespace { |
| |
| TEST(TransformationFunctionCallTest, BasicTest) { |
| std::string shader = R"( |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %4 "main" |
| OpExecutionMode %4 OriginUpperLeft |
| OpSource ESSL 310 |
| %2 = OpTypeVoid |
| %3 = OpTypeFunction %2 |
| %6 = OpTypeInt 32 1 |
| %7 = OpTypePointer Function %6 |
| %8 = OpTypeFunction %6 %7 |
| %12 = OpTypeFloat 32 |
| %13 = OpTypePointer Function %12 |
| %14 = OpTypeFunction %6 %7 %13 |
| %27 = OpConstant %6 1 |
| %50 = OpConstant %12 1 |
| %57 = OpTypeBool |
| %58 = OpConstantFalse %57 |
| %204 = OpUndef %6 |
| %4 = OpFunction %2 None %3 |
| %5 = OpLabel |
| %61 = OpVariable %7 Function |
| %62 = OpVariable %7 Function |
| %65 = OpVariable %13 Function |
| %66 = OpVariable %7 Function |
| %68 = OpVariable %13 Function |
| %71 = OpVariable %7 Function |
| %72 = OpVariable %13 Function |
| %73 = OpVariable %7 Function |
| %75 = OpVariable %13 Function |
| %78 = OpVariable %7 Function |
| %98 = OpAccessChain %7 %71 |
| %99 = OpCopyObject %7 %71 |
| OpSelectionMerge %60 None |
| OpBranchConditional %58 %59 %60 |
| %59 = OpLabel |
| OpBranch %60 |
| %60 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| %10 = OpFunction %6 None %8 |
| %9 = OpFunctionParameter %7 |
| %11 = OpLabel |
| %26 = OpLoad %6 %9 |
| %28 = OpIAdd %6 %26 %27 |
| OpSelectionMerge %97 None |
| OpBranchConditional %58 %96 %97 |
| %96 = OpLabel |
| OpBranch %97 |
| %97 = OpLabel |
| OpReturnValue %28 |
| OpFunctionEnd |
| %17 = OpFunction %6 None %14 |
| %15 = OpFunctionParameter %7 |
| %16 = OpFunctionParameter %13 |
| %18 = OpLabel |
| %31 = OpVariable %7 Function |
| %32 = OpLoad %6 %15 |
| OpStore %31 %32 |
| %33 = OpFunctionCall %6 %10 %31 |
| OpReturnValue %33 |
| OpFunctionEnd |
| %21 = OpFunction %6 None %14 |
| %19 = OpFunctionParameter %7 |
| %20 = OpFunctionParameter %13 |
| %22 = OpLabel |
| %36 = OpLoad %6 %19 |
| %37 = OpLoad %12 %20 |
| %38 = OpConvertFToS %6 %37 |
| %39 = OpIAdd %6 %36 %38 |
| OpReturnValue %39 |
| OpFunctionEnd |
| %24 = OpFunction %6 None %8 |
| %23 = OpFunctionParameter %7 |
| %25 = OpLabel |
| %44 = OpVariable %7 Function |
| %46 = OpVariable %13 Function |
| %51 = OpVariable %7 Function |
| %52 = OpVariable %13 Function |
| %42 = OpLoad %6 %23 |
| %43 = OpConvertSToF %12 %42 |
| %45 = OpLoad %6 %23 |
| OpStore %44 %45 |
| OpStore %46 %43 |
| %47 = OpFunctionCall %6 %17 %44 %46 |
| %48 = OpLoad %6 %23 |
| %49 = OpIAdd %6 %48 %27 |
| OpStore %51 %49 |
| OpStore %52 %50 |
| %53 = OpFunctionCall %6 %17 %51 %52 |
| %54 = OpIAdd %6 %47 %53 |
| OpReturnValue %54 |
| OpFunctionEnd |
| %200 = OpFunction %6 None %14 |
| %201 = OpFunctionParameter %7 |
| %202 = OpFunctionParameter %13 |
| %203 = OpLabel |
| OpSelectionMerge %206 None |
| OpBranchConditional %58 %205 %206 |
| %205 = OpLabel |
| OpBranch %206 |
| %206 = OpLabel |
| OpReturnValue %204 |
| OpFunctionEnd |
| )"; |
| |
| const auto env = SPV_ENV_UNIVERSAL_1_4; |
| const auto consumer = nullptr; |
| const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); |
| spvtools::ValidatorOptions validator_options; |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, |
| kConsoleMessageConsumer)); |
| TransformationContext transformation_context( |
| MakeUnique<FactManager>(context.get()), validator_options); |
| transformation_context.GetFactManager()->AddFactBlockIsDead(59); |
| transformation_context.GetFactManager()->AddFactBlockIsDead(11); |
| transformation_context.GetFactManager()->AddFactBlockIsDead(18); |
| transformation_context.GetFactManager()->AddFactBlockIsDead(25); |
| transformation_context.GetFactManager()->AddFactBlockIsDead(96); |
| transformation_context.GetFactManager()->AddFactBlockIsDead(205); |
| transformation_context.GetFactManager()->AddFactFunctionIsLivesafe(21); |
| transformation_context.GetFactManager()->AddFactFunctionIsLivesafe(200); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 71); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 72); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 19); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 20); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 23); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 44); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 46); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 51); |
| transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant( |
| 52); |
| |
| // Livesafe functions with argument types: 21(7, 13), 200(7, 13) |
| // Non-livesafe functions with argument types: 4(), 10(7), 17(7, 13), 24(7) |
| // Call graph edges: |
| // 17 -> 10 |
| // 24 -> 17 |
| |
| // Bad transformations |
| // Too many arguments |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 21, {71, 72, 71}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Too few arguments |
| ASSERT_FALSE(TransformationFunctionCall( |
| 100, 21, {71}, MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Arguments are the wrong way around (types do not match) |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 21, {72, 71}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // 21 is not an appropriate argument |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 21, {21, 72}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // 300 does not exist |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 21, {300, 72}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // 71 is not a function |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 71, {71, 72}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // 500 does not exist |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 500, {71, 72}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Id is not fresh |
| ASSERT_FALSE( |
| TransformationFunctionCall(21, 21, {71, 72}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Access chain as pointer parameter |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 21, {98, 72}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Copied object as pointer parameter |
| ASSERT_FALSE( |
| TransformationFunctionCall(100, 21, {99, 72}, |
| MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Non-livesafe called from original live block |
| ASSERT_FALSE( |
| TransformationFunctionCall( |
| 100, 10, {71}, MakeInstructionDescriptor(99, SpvOpSelectionMerge, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Non-livesafe called from livesafe function |
| ASSERT_FALSE( |
| TransformationFunctionCall( |
| 100, 10, {19}, MakeInstructionDescriptor(38, SpvOpConvertFToS, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Livesafe function called with pointer to non-arbitrary local variable |
| ASSERT_FALSE( |
| TransformationFunctionCall( |
| 100, 21, {61, 72}, MakeInstructionDescriptor(38, SpvOpConvertFToS, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Direct recursion |
| ASSERT_FALSE(TransformationFunctionCall( |
| 100, 4, {}, MakeInstructionDescriptor(59, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Indirect recursion |
| ASSERT_FALSE(TransformationFunctionCall( |
| 100, 24, {9}, MakeInstructionDescriptor(96, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| // Parameter 23 is not available at the call site |
| ASSERT_FALSE( |
| TransformationFunctionCall(104, 10, {23}, |
| MakeInstructionDescriptor(205, SpvOpBranch, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| |
| // Good transformations |
| { |
| // Livesafe called from dead block: fine |
| TransformationFunctionCall transformation( |
| 100, 21, {71, 72}, MakeInstructionDescriptor(59, SpvOpBranch, 0)); |
| ASSERT_TRUE( |
| transformation.IsApplicable(context.get(), transformation_context)); |
| ApplyAndCheckFreshIds(transformation, context.get(), |
| &transformation_context); |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( |
| context.get(), validator_options, kConsoleMessageConsumer)); |
| } |
| { |
| // Livesafe called from original live block: fine |
| TransformationFunctionCall transformation( |
| 101, 21, {71, 72}, MakeInstructionDescriptor(98, SpvOpAccessChain, 0)); |
| ASSERT_TRUE( |
| transformation.IsApplicable(context.get(), transformation_context)); |
| ApplyAndCheckFreshIds(transformation, context.get(), |
| &transformation_context); |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( |
| context.get(), validator_options, kConsoleMessageConsumer)); |
| } |
| { |
| // Livesafe called from livesafe function: fine |
| TransformationFunctionCall transformation( |
| 102, 200, {19, 20}, MakeInstructionDescriptor(36, SpvOpLoad, 0)); |
| ASSERT_TRUE( |
| transformation.IsApplicable(context.get(), transformation_context)); |
| ApplyAndCheckFreshIds(transformation, context.get(), |
| &transformation_context); |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( |
| context.get(), validator_options, kConsoleMessageConsumer)); |
| } |
| { |
| // Dead called from dead block in injected function: fine |
| TransformationFunctionCall transformation( |
| 103, 10, {23}, MakeInstructionDescriptor(45, SpvOpLoad, 0)); |
| ASSERT_TRUE( |
| transformation.IsApplicable(context.get(), transformation_context)); |
| ApplyAndCheckFreshIds(transformation, context.get(), |
| &transformation_context); |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( |
| context.get(), validator_options, kConsoleMessageConsumer)); |
| } |
| { |
| // Non-livesafe called from dead block in livesafe function: OK |
| TransformationFunctionCall transformation( |
| 104, 10, {201}, MakeInstructionDescriptor(205, SpvOpBranch, 0)); |
| ASSERT_TRUE( |
| transformation.IsApplicable(context.get(), transformation_context)); |
| ApplyAndCheckFreshIds(transformation, context.get(), |
| &transformation_context); |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( |
| context.get(), validator_options, kConsoleMessageConsumer)); |
| } |
| { |
| // Livesafe called from dead block with non-arbitrary parameter |
| TransformationFunctionCall transformation( |
| 105, 21, {62, 65}, MakeInstructionDescriptor(59, SpvOpBranch, 0)); |
| ASSERT_TRUE( |
| transformation.IsApplicable(context.get(), transformation_context)); |
| ApplyAndCheckFreshIds(transformation, context.get(), |
| &transformation_context); |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( |
| context.get(), validator_options, kConsoleMessageConsumer)); |
| } |
| |
| std::string after_transformation = R"( |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %4 "main" |
| OpExecutionMode %4 OriginUpperLeft |
| OpSource ESSL 310 |
| %2 = OpTypeVoid |
| %3 = OpTypeFunction %2 |
| %6 = OpTypeInt 32 1 |
| %7 = OpTypePointer Function %6 |
| %8 = OpTypeFunction %6 %7 |
| %12 = OpTypeFloat 32 |
| %13 = OpTypePointer Function %12 |
| %14 = OpTypeFunction %6 %7 %13 |
| %27 = OpConstant %6 1 |
| %50 = OpConstant %12 1 |
| %57 = OpTypeBool |
| %58 = OpConstantFalse %57 |
| %204 = OpUndef %6 |
| %4 = OpFunction %2 None %3 |
| %5 = OpLabel |
| %61 = OpVariable %7 Function |
| %62 = OpVariable %7 Function |
| %65 = OpVariable %13 Function |
| %66 = OpVariable %7 Function |
| %68 = OpVariable %13 Function |
| %71 = OpVariable %7 Function |
| %72 = OpVariable %13 Function |
| %73 = OpVariable %7 Function |
| %75 = OpVariable %13 Function |
| %78 = OpVariable %7 Function |
| %101 = OpFunctionCall %6 %21 %71 %72 |
| %98 = OpAccessChain %7 %71 |
| %99 = OpCopyObject %7 %71 |
| OpSelectionMerge %60 None |
| OpBranchConditional %58 %59 %60 |
| %59 = OpLabel |
| %100 = OpFunctionCall %6 %21 %71 %72 |
| %105 = OpFunctionCall %6 %21 %62 %65 |
| OpBranch %60 |
| %60 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| %10 = OpFunction %6 None %8 |
| %9 = OpFunctionParameter %7 |
| %11 = OpLabel |
| %26 = OpLoad %6 %9 |
| %28 = OpIAdd %6 %26 %27 |
| OpSelectionMerge %97 None |
| OpBranchConditional %58 %96 %97 |
| %96 = OpLabel |
| OpBranch %97 |
| %97 = OpLabel |
| OpReturnValue %28 |
| OpFunctionEnd |
| %17 = OpFunction %6 None %14 |
| %15 = OpFunctionParameter %7 |
| %16 = OpFunctionParameter %13 |
| %18 = OpLabel |
| %31 = OpVariable %7 Function |
| %32 = OpLoad %6 %15 |
| OpStore %31 %32 |
| %33 = OpFunctionCall %6 %10 %31 |
| OpReturnValue %33 |
| OpFunctionEnd |
| %21 = OpFunction %6 None %14 |
| %19 = OpFunctionParameter %7 |
| %20 = OpFunctionParameter %13 |
| %22 = OpLabel |
| %102 = OpFunctionCall %6 %200 %19 %20 |
| %36 = OpLoad %6 %19 |
| %37 = OpLoad %12 %20 |
| %38 = OpConvertFToS %6 %37 |
| %39 = OpIAdd %6 %36 %38 |
| OpReturnValue %39 |
| OpFunctionEnd |
| %24 = OpFunction %6 None %8 |
| %23 = OpFunctionParameter %7 |
| %25 = OpLabel |
| %44 = OpVariable %7 Function |
| %46 = OpVariable %13 Function |
| %51 = OpVariable %7 Function |
| %52 = OpVariable %13 Function |
| %42 = OpLoad %6 %23 |
| %43 = OpConvertSToF %12 %42 |
| %103 = OpFunctionCall %6 %10 %23 |
| %45 = OpLoad %6 %23 |
| OpStore %44 %45 |
| OpStore %46 %43 |
| %47 = OpFunctionCall %6 %17 %44 %46 |
| %48 = OpLoad %6 %23 |
| %49 = OpIAdd %6 %48 %27 |
| OpStore %51 %49 |
| OpStore %52 %50 |
| %53 = OpFunctionCall %6 %17 %51 %52 |
| %54 = OpIAdd %6 %47 %53 |
| OpReturnValue %54 |
| OpFunctionEnd |
| %200 = OpFunction %6 None %14 |
| %201 = OpFunctionParameter %7 |
| %202 = OpFunctionParameter %13 |
| %203 = OpLabel |
| OpSelectionMerge %206 None |
| OpBranchConditional %58 %205 %206 |
| %205 = OpLabel |
| %104 = OpFunctionCall %6 %10 %201 |
| OpBranch %206 |
| %206 = OpLabel |
| OpReturnValue %204 |
| OpFunctionEnd |
| )"; |
| ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); |
| } |
| |
| TEST(TransformationFunctionCallTest, DoNotInvokeEntryPoint) { |
| std::string shader = R"( |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %4 "main" |
| OpExecutionMode %4 OriginUpperLeft |
| OpSource ESSL 310 |
| %2 = OpTypeVoid |
| %3 = OpTypeFunction %2 |
| %4 = OpFunction %2 None %3 |
| %5 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| %10 = OpFunction %2 None %3 |
| %11 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| const auto env = SPV_ENV_UNIVERSAL_1_4; |
| const auto consumer = nullptr; |
| const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); |
| spvtools::ValidatorOptions validator_options; |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, |
| kConsoleMessageConsumer)); |
| TransformationContext transformation_context( |
| MakeUnique<FactManager>(context.get()), validator_options); |
| transformation_context.GetFactManager()->AddFactBlockIsDead(11); |
| |
| // 4 is an entry point, so it is not legal for it to be the target of a call. |
| ASSERT_FALSE(TransformationFunctionCall( |
| 100, 4, {}, MakeInstructionDescriptor(11, SpvOpReturn, 0)) |
| .IsApplicable(context.get(), transformation_context)); |
| } |
| |
| } // namespace |
| } // namespace fuzz |
| } // namespace spvtools |