spirv-fuzz: Handle capabilities during module donation (#3651)
Fixes #3648.
diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp
index aa0e243..cbc69c6 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.cpp
+++ b/source/fuzz/fuzzer_pass_donate_modules.cpp
@@ -84,6 +84,16 @@
void FuzzerPassDonateModules::DonateSingleModule(
opt::IRContext* donor_ir_context, bool make_livesafe) {
+ // Check that the donated module has capabilities, supported by the recipient
+ // module.
+ for (const auto& capability_inst : donor_ir_context->capabilities()) {
+ auto capability =
+ static_cast<SpvCapability>(capability_inst.GetSingleWordInOperand(0));
+ if (!GetIRContext()->get_feature_mgr()->HasCapability(capability)) {
+ return;
+ }
+ }
+
// The ids used by the donor module may very well clash with ids defined in
// the recipient module. Furthermore, some instructions defined in the donor
// module will be equivalent to instructions defined in the recipient module,
diff --git a/test/fuzz/fuzzer_pass_donate_modules_test.cpp b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
index 0be3d5a..34dba86 100644
--- a/test/fuzz/fuzzer_pass_donate_modules_test.cpp
+++ b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "source/fuzz/fuzzer_pass_donate_modules.h"
+
#include <algorithm>
-#include "source/fuzz/fuzzer_pass_donate_modules.h"
#include "source/fuzz/pseudo_random_generator.h"
#include "test/fuzz/fuzz_test_util.h"
@@ -1902,6 +1903,131 @@
ASSERT_TRUE(IsValid(env, recipient_context.get()));
}
+TEST(FuzzerPassDonateModulesTest, HandlesCapabilities) {
+ std::string donor_shader = R"(
+ OpCapability VariablePointersStorageBuffer
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %11 = OpConstant %6 23
+ %7 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpBranch %9
+
+ %9 = OpLabel
+ %10 = OpPhi %7 %8 %5
+ OpStore %10 %11
+ OpReturn
+
+ OpFunctionEnd
+ )";
+
+ std::string recipient_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
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto recipient_context =
+ BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+ const auto donor_context =
+ BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ PseudoRandomGenerator rng(0);
+ FuzzerContext fuzzer_context(&rng, 100);
+ protobufs::TransformationSequence transformation_sequence;
+
+ FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+ &transformation_context, &fuzzer_context,
+ &transformation_sequence, {});
+
+ ASSERT_TRUE(donor_context->get_feature_mgr()->HasCapability(
+ SpvCapabilityVariablePointersStorageBuffer));
+ ASSERT_FALSE(recipient_context->get_feature_mgr()->HasCapability(
+ SpvCapabilityVariablePointersStorageBuffer));
+
+ fuzzer_pass.DonateSingleModule(donor_context.get(), false);
+
+ // Check that recipient module hasn't changed.
+ ASSERT_TRUE(IsEqual(env, recipient_shader, recipient_context.get()));
+
+ // Add the missing capability.
+ //
+ // We are adding VariablePointers to test the case when donor and recipient
+ // have different OpCapability instructions but the same capabilities. In our
+ // example, VariablePointers implicitly declares
+ // VariablePointersStorageBuffer. Thus, two modules must be compatible.
+ recipient_context->AddCapability(SpvCapabilityVariablePointers);
+
+ ASSERT_TRUE(donor_context->get_feature_mgr()->HasCapability(
+ SpvCapabilityVariablePointersStorageBuffer));
+ ASSERT_TRUE(recipient_context->get_feature_mgr()->HasCapability(
+ SpvCapabilityVariablePointersStorageBuffer));
+
+ fuzzer_pass.DonateSingleModule(donor_context.get(), false);
+
+ // Check that donation was successful.
+ ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ OpCapability VariablePointers
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %100 = OpTypeFloat 32
+ %101 = OpConstant %100 23
+ %102 = OpTypePointer Function %100
+ %105 = OpConstant %100 0
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %103 = OpFunction %2 None %3
+ %104 = OpLabel
+ %106 = OpVariable %102 Function %105
+ OpBranch %107
+ %107 = OpLabel
+ %108 = OpPhi %102 %106 %104
+ OpStore %108 %101
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get()));
+}
+
} // namespace
} // namespace fuzz
} // namespace spvtools