SPIRV-Tools/test/fuzz/transformation_function_call_test.cpp
Alastair Donaldson 502e982956
spirv-fuzz: Fix to TransformationInlineFunction (#3913)
This fixes a problem where TransformationInlineFunction could lead to
distinct instructions having identical unique ids. It adds a validity
check to detect this problem in general.

Fixes #3911.
2020-10-16 22:58:09 +01:00

476 lines
18 KiB
C++

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