SPIRV-Tools/test/fuzz/shrinker_test.cpp
alan-baker d35a78db57
Switch SPIRV-Tools to use spirv.hpp11 internally (#4981)
Fixes #4960

* Switches to using enum classes with an underlying type to avoid
  undefined behaviour
2022-11-04 17:27:10 -04:00

383 lines
13 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/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 = fuzzerutil::kSilentMessageConsumer;
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));
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
false);
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, false, {});
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() == spv::Op::OpNot) {
found_op_not = true;
} else if (inst.opcode() == spv::Op::OpFunctionCall) {
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(spv::Op::OpVariable, inst.opcode());
ASSERT_EQ(11, inst.result_id());
break;
case 1:
ASSERT_EQ(spv::Op::OpStore, inst.opcode());
break;
case 2:
ASSERT_EQ(spv::Op::OpLoad, inst.opcode());
ASSERT_EQ(12, inst.result_id());
break;
case 3:
ASSERT_EQ(spv::Op::OpStore, inst.opcode());
break;
case 4:
ASSERT_EQ(spv::Op::OpReturn, 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 spv::Op::OpVariable:
case spv::Op::OpNot:
case spv::Op::OpReturn:
case spv::Op::OpReturnValue:
case spv::Op::OpFunctionCall:
// These are the only instructions we expect to see.
break;
default:
FAIL();
}
}
}
}
}
}
TEST(ShrinkerTest, HitStepLimitWhenReducingAddedFunctions) {
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
%48 = OpConstant %6 3
%4 = OpFunction %2 None %3
%5 = OpLabel
%52 = OpCopyObject %6 %48
%53 = OpCopyObject %6 %52
%54 = OpCopyObject %6 %53
%55 = OpCopyObject %6 %54
%56 = OpCopyObject %6 %55
%57 = OpCopyObject %6 %56
%58 = OpCopyObject %6 %48
%59 = OpCopyObject %6 %58
%60 = OpCopyObject %6 %59
%61 = OpCopyObject %6 %60
%62 = OpCopyObject %6 %61
%63 = OpCopyObject %6 %62
%64 = OpCopyObject %6 %48
OpReturn
OpFunctionEnd
)";
spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = fuzzerutil::kSilentMessageConsumer;
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));
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
false);
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, false, {});
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 {
auto temp_ir_context =
BuildModule(env, consumer, binary.data(), binary.size());
uint32_t copy_object_count = 0;
temp_ir_context->module()->ForEachInst(
[&copy_object_count](opt::Instruction* inst) {
if (inst->opcode() == spv::Op::OpCopyObject) {
copy_object_count++;
}
});
return copy_object_count >= 8;
};
auto shrinker_result =
Shrinker(env, consumer, reference_binary, no_facts, transformations,
interestingness_function, 30, true, validator_options)
.Run();
ASSERT_EQ(Shrinker::ShrinkerResultStatus::kStepLimitReached,
shrinker_result.status);
}
} // namespace
} // namespace fuzz
} // namespace spvtools