// 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/call_graph.h" #include "gtest/gtest.h" #include "source/fuzz/fuzzer_util.h" #include "test/fuzz/fuzz_test_util.h" namespace spvtools { namespace fuzz { namespace { // The SPIR-V came from this GLSL, slightly modified // (main is %2, A is %35, B is %48, C is %50, D is %61): // // #version 310 es // // int A (int x) { // return x + 1; // } // // void D() { // } // // void C() { // int x = 0; // int y = 0; // // while (x < 10) { // while (y < 10) { // y = A(y); // } // x = A(x); // } // } // // void B () { // int x = 0; // int y = 0; // // while (x < 10) { // D(); // while (y < 10) { // y = A(y); // C(); // } // x++; // } // // } // // void main() // { // int x = 0; // int y = 0; // int z = 0; // // while (x < 10) { // while(y < 10) { // y = A(x); // while (z < 10) { // z = A(z); // } // } // x += 2; // } // // B(); // C(); // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource ESSL 310 %3 = OpTypeVoid %4 = OpTypeFunction %3 %5 = OpTypeInt 32 1 %6 = OpTypePointer Function %5 %7 = OpTypeFunction %5 %6 %8 = OpConstant %5 1 %9 = OpConstant %5 0 %10 = OpConstant %5 10 %11 = OpTypeBool %12 = OpConstant %5 2 %2 = OpFunction %3 None %4 %13 = OpLabel %14 = OpVariable %6 Function %15 = OpVariable %6 Function %16 = OpVariable %6 Function %17 = OpVariable %6 Function %18 = OpVariable %6 Function OpStore %14 %9 OpStore %15 %9 OpStore %16 %9 OpBranch %19 %19 = OpLabel OpLoopMerge %20 %21 None OpBranch %22 %22 = OpLabel %23 = OpLoad %5 %14 %24 = OpSLessThan %11 %23 %10 OpBranchConditional %24 %25 %20 %25 = OpLabel OpBranch %26 %26 = OpLabel OpLoopMerge %27 %28 None OpBranch %29 %29 = OpLabel %30 = OpLoad %5 %15 %31 = OpSLessThan %11 %30 %10 OpBranchConditional %31 %32 %27 %32 = OpLabel %33 = OpLoad %5 %14 OpStore %17 %33 %34 = OpFunctionCall %5 %35 %17 OpStore %15 %34 OpBranch %36 %36 = OpLabel OpLoopMerge %37 %38 None OpBranch %39 %39 = OpLabel %40 = OpLoad %5 %16 %41 = OpSLessThan %11 %40 %10 OpBranchConditional %41 %42 %37 %42 = OpLabel %43 = OpLoad %5 %16 OpStore %18 %43 %44 = OpFunctionCall %5 %35 %18 OpStore %16 %44 OpBranch %38 %38 = OpLabel OpBranch %36 %37 = OpLabel OpBranch %28 %28 = OpLabel OpBranch %26 %27 = OpLabel %45 = OpLoad %5 %14 %46 = OpIAdd %5 %45 %12 OpStore %14 %46 OpBranch %21 %21 = OpLabel OpBranch %19 %20 = OpLabel %47 = OpFunctionCall %3 %48 %49 = OpFunctionCall %3 %50 OpReturn OpFunctionEnd %35 = OpFunction %5 None %7 %51 = OpFunctionParameter %6 %52 = OpLabel %53 = OpLoad %5 %51 %54 = OpIAdd %5 %53 %8 OpReturnValue %54 OpFunctionEnd %48 = OpFunction %3 None %4 %55 = OpLabel %56 = OpVariable %6 Function %57 = OpVariable %6 Function %58 = OpVariable %6 Function OpStore %56 %9 OpStore %57 %9 OpBranch %59 %59 = OpLabel %60 = OpFunctionCall %3 %61 OpLoopMerge %62 %63 None OpBranch %64 %64 = OpLabel OpLoopMerge %65 %66 None OpBranch %67 %67 = OpLabel %68 = OpLoad %5 %57 %69 = OpSLessThan %11 %68 %10 OpBranchConditional %69 %70 %65 %70 = OpLabel %71 = OpLoad %5 %57 OpStore %58 %71 %72 = OpFunctionCall %5 %35 %58 OpStore %57 %72 %73 = OpFunctionCall %3 %50 OpBranch %66 %66 = OpLabel OpBranch %64 %65 = OpLabel %74 = OpLoad %5 %56 %75 = OpIAdd %5 %74 %8 OpStore %56 %75 OpBranch %63 %63 = OpLabel %76 = OpLoad %5 %56 %77 = OpSLessThan %11 %76 %10 OpBranchConditional %77 %59 %62 %62 = OpLabel OpReturn OpFunctionEnd %50 = OpFunction %3 None %4 %78 = OpLabel %79 = OpVariable %6 Function %80 = OpVariable %6 Function %81 = OpVariable %6 Function %82 = OpVariable %6 Function OpStore %79 %9 OpStore %80 %9 OpBranch %83 %83 = OpLabel OpLoopMerge %84 %85 None OpBranch %86 %86 = OpLabel %87 = OpLoad %5 %79 %88 = OpSLessThan %11 %87 %10 OpBranchConditional %88 %89 %84 %89 = OpLabel OpBranch %90 %90 = OpLabel OpLoopMerge %91 %92 None OpBranch %93 %93 = OpLabel %94 = OpLoad %5 %80 %95 = OpSLessThan %11 %94 %10 OpBranchConditional %95 %96 %91 %96 = OpLabel %97 = OpLoad %5 %80 OpStore %81 %97 %98 = OpFunctionCall %5 %35 %81 OpStore %80 %98 OpBranch %92 %92 = OpLabel OpBranch %90 %91 = OpLabel %99 = OpLoad %5 %79 OpStore %82 %99 %100 = OpFunctionCall %5 %35 %82 OpStore %79 %100 OpBranch %85 %85 = OpLabel OpBranch %83 %84 = OpLabel OpReturn OpFunctionEnd %61 = OpFunction %3 None %4 %101 = OpLabel OpReturn OpFunctionEnd )"; // We have that: // main calls: // - A (maximum loop nesting depth of function call: 3) // - B (0) // - C (0) // A calls nothing. // B calls: // - A (2) // - C (2) // - D (1) // C calls: // - A (2) // D calls nothing. TEST(CallGraphTest, FunctionInDegree) { const auto env = SPV_ENV_UNIVERSAL_1_5; 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)); const auto graph = CallGraph(context.get()); const auto& function_in_degree = graph.GetFunctionInDegree(); // Check the in-degrees of, in order: main, A, B, C, D. ASSERT_EQ(function_in_degree.at(2), 0); ASSERT_EQ(function_in_degree.at(35), 3); ASSERT_EQ(function_in_degree.at(48), 1); ASSERT_EQ(function_in_degree.at(50), 2); ASSERT_EQ(function_in_degree.at(61), 1); } TEST(CallGraphTest, DirectCallees) { const auto env = SPV_ENV_UNIVERSAL_1_5; 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)); const auto graph = CallGraph(context.get()); // Check the callee sets of, in order: main, A, B, C, D. ASSERT_EQ(graph.GetDirectCallees(2), std::set({35, 48, 50})); ASSERT_EQ(graph.GetDirectCallees(35), std::set({})); ASSERT_EQ(graph.GetDirectCallees(48), std::set({35, 50, 61})); ASSERT_EQ(graph.GetDirectCallees(50), std::set({35})); ASSERT_EQ(graph.GetDirectCallees(61), std::set({})); } TEST(CallGraphTest, IndirectCallees) { const auto env = SPV_ENV_UNIVERSAL_1_5; 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)); const auto graph = CallGraph(context.get()); // Check the callee sets of, in order: main, A, B, C, D. ASSERT_EQ(graph.GetIndirectCallees(2), std::set({35, 48, 50, 61})); ASSERT_EQ(graph.GetDirectCallees(35), std::set({})); ASSERT_EQ(graph.GetDirectCallees(48), std::set({35, 50, 61})); ASSERT_EQ(graph.GetDirectCallees(50), std::set({35})); ASSERT_EQ(graph.GetDirectCallees(61), std::set({})); } TEST(CallGraphTest, TopologicalOrder) { const auto env = SPV_ENV_UNIVERSAL_1_5; 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)); const auto graph = CallGraph(context.get()); const auto& topological_ordering = graph.GetFunctionsInTopologicalOrder(); // The possible topological orderings are: // - main, B, D, C, A // - main, B, C, D, A // - main, B, C, A, D ASSERT_TRUE( topological_ordering == std::vector({2, 48, 61, 50, 35}) || topological_ordering == std::vector({2, 48, 50, 61, 35}) || topological_ordering == std::vector({2, 48, 50, 35, 61})); } TEST(CallGraphTest, LoopNestingDepth) { const auto env = SPV_ENV_UNIVERSAL_1_5; 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)); const auto graph = CallGraph(context.get()); // Check the maximum loop nesting depth for function calls to, in order: // main, A, B, C, D ASSERT_EQ(graph.GetMaxCallNestingDepth(2), 0); ASSERT_EQ(graph.GetMaxCallNestingDepth(35), 4); ASSERT_EQ(graph.GetMaxCallNestingDepth(48), 0); ASSERT_EQ(graph.GetMaxCallNestingDepth(50), 2); ASSERT_EQ(graph.GetMaxCallNestingDepth(61), 1); } } // namespace } // namespace fuzz } // namespace spvtools