1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <android-base/logging.h>
18 
19 #include <cstdlib>
20 #include <optional>
21 #include <utility>
22 
23 #include "Converter.h"
24 #include "Model.pb.h"
25 #include "NeuralNetworksWrapper.h"
26 #include "TestHarness.h"
27 #include "src/libfuzzer/libfuzzer_macro.h"
28 
29 namespace {
30 
31 using ::android::nn::fuzz::convertToTestModel;
32 using ::android_nn_fuzz::Test;
33 using ::test_helper::TestModel;
34 using namespace ::android::nn::wrapper;
35 using namespace test_helper;
36 
getOperandType(const TestOperand & op)37 OperandType getOperandType(const TestOperand& op) {
38     auto dims = op.dimensions;
39     if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
40         return OperandType(
41                 static_cast<Type>(op.type), dims,
42                 SymmPerChannelQuantParams(op.channelQuant.scales, op.channelQuant.channelDim));
43     } else {
44         return OperandType(static_cast<Type>(op.type), dims, op.scale, op.zeroPoint);
45     }
46 }
47 
CreateModel(const TestModel & testModel)48 std::optional<Model> CreateModel(const TestModel& testModel) {
49     Model model;
50 
51     // Operands.
52     // TODO(b/148605565): Add control flow support
53     CHECK_EQ(testModel.referenced.size(), 0u) << "Subgraphs not supported";
54     for (const auto& operand : testModel.main.operands) {
55         auto type = getOperandType(operand);
56         auto index = model.addOperand(&type);
57 
58         switch (operand.lifetime) {
59             case TestOperandLifeTime::CONSTANT_COPY:
60             case TestOperandLifeTime::CONSTANT_REFERENCE:
61                 model.setOperandValue(index, operand.data.get<void>(), operand.data.size());
62                 break;
63             case TestOperandLifeTime::NO_VALUE:
64                 model.setOperandValue(index, nullptr, 0);
65                 break;
66             case TestOperandLifeTime::SUBGRAPH: {
67                 CHECK(false);
68             } break;
69             case TestOperandLifeTime::SUBGRAPH_INPUT:
70             case TestOperandLifeTime::SUBGRAPH_OUTPUT:
71             case TestOperandLifeTime::TEMPORARY_VARIABLE:
72                 // Nothing to do here.
73                 break;
74         }
75         if (!model.isValid()) return std::nullopt;
76     }
77 
78     // Operations.
79     CHECK_EQ(testModel.referenced.size(), 0u) << "Subgraphs not supported";
80     for (const auto& operation : testModel.main.operations) {
81         model.addOperation(static_cast<int>(operation.type), operation.inputs, operation.outputs);
82         if (!model.isValid()) return std::nullopt;
83     }
84 
85     // Inputs and outputs.
86     model.identifyInputsAndOutputs(testModel.main.inputIndexes, testModel.main.outputIndexes);
87     if (!model.isValid()) return std::nullopt;
88 
89     // Relaxed computation.
90     model.relaxComputationFloat32toFloat16(testModel.isRelaxed);
91     if (!model.isValid()) return std::nullopt;
92 
93     if (model.finish() != Result::NO_ERROR) {
94         return std::nullopt;
95     }
96 
97     return model;
98 }
99 
CreateCompilation(const Model & model)100 std::optional<Compilation> CreateCompilation(const Model& model) {
101     Compilation compilation(&model);
102     if (compilation.finish() != Result::NO_ERROR) {
103         return std::nullopt;
104     }
105     return compilation;
106 }
107 
CreateExecution(const Compilation & compilation,const TestModel & testModel)108 std::optional<Execution> CreateExecution(const Compilation& compilation,
109                                          const TestModel& testModel) {
110     Execution execution(&compilation);
111 
112     // Model inputs.
113     for (uint32_t i = 0; i < testModel.main.inputIndexes.size(); i++) {
114         const auto& operand = testModel.main.operands[testModel.main.inputIndexes[i]];
115         if (execution.setInput(i, operand.data.get<void>(), operand.data.size()) !=
116             Result::NO_ERROR) {
117             return std::nullopt;
118         }
119     }
120 
121     // Model outputs.
122     for (uint32_t i = 0; i < testModel.main.outputIndexes.size(); i++) {
123         const auto& operand = testModel.main.operands[testModel.main.outputIndexes[i]];
124         if (execution.setOutput(i, const_cast<void*>(operand.data.get<void>()),
125                                 operand.data.size()) != Result::NO_ERROR) {
126             return std::nullopt;
127         }
128     }
129 
130     return execution;
131 }
132 
runTest(const TestModel & testModel)133 void runTest(const TestModel& testModel) {
134     // set up model
135     auto model = CreateModel(testModel);
136     if (!model.has_value()) {
137         return;
138     }
139 
140     // set up compilation
141     auto compilation = CreateCompilation(*model);
142     if (!compilation.has_value()) {
143         return;
144     }
145 
146     // set up execution
147     auto execution = CreateExecution(*compilation, testModel);
148     if (!execution.has_value()) {
149         return;
150     }
151 
152     // perform execution
153     execution->compute();
154 }
155 
156 }  // anonymous namespace
157 
DEFINE_PROTO_FUZZER(const Test & model)158 DEFINE_PROTO_FUZZER(const Test& model) {
159     const TestModel testModel = convertToTestModel(model);
160     runTest(testModel);
161 }
162