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 #ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H
18 #define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H
19 
20 #include <android/log.h>
21 
22 #include <chrono>
23 #include <fstream>
24 #include <limits>
25 #include <memory>
26 #include <random>
27 #include <sstream>
28 #include <string>
29 #include <vector>
30 
31 #include "RandomGraphGenerator.h"
32 #include "RandomVariable.h"
33 #include "TestHarness.h"
34 #include "TestNeuralNetworksWrapper.h"
35 
36 namespace android {
37 namespace nn {
38 namespace fuzzing_test {
39 
40 #define NN_FUZZER_LOG_INIT(filename) Logger::get()->init((filename))
41 #define NN_FUZZER_LOG_WRITE_FATAL_TO_SYSLOG(logTag) \
42     LoggerStream::writeAbortMessageToSystemLog(logTag)
43 #define NN_FUZZER_LOG_CLOSE Logger::get()->close()
44 #define NN_FUZZER_LOG              \
45     if (!Logger::get()->enabled()) \
46         ;                          \
47     else                           \
48         LoggerStream(false) << alignedString(__FUNCTION__, 20)
49 #define NN_FUZZER_CHECK(condition)                                                             \
50     if ((condition))                                                                           \
51         ;                                                                                      \
52     else                                                                                       \
53         LoggerStream(true) << alignedString(__FUNCTION__, 20) << "Check failed " << #condition \
54                            << ": "
55 
56 // A Singleton manages the global configurations of logging.
57 class Logger {
58    public:
get()59     static Logger* get() {
60         static Logger instance;
61         return &instance;
62     }
init(const std::string & filename)63     void init(const std::string& filename) {
64         os.open(filename);
65         mStart = std::chrono::high_resolution_clock::now();
66     }
enabled()67     bool enabled() { return os.is_open(); }
close()68     void close() {
69         if (os.is_open()) os.close();
70     }
log(const std::string & str)71     void log(const std::string& str) {
72         if (os.is_open()) os << getElapsedTime() << str << std::flush;
73     }
74 
75    private:
76     Logger() = default;
77     Logger(const Logger&) = delete;
78     Logger& operator=(const Logger&) = delete;
79     std::string getElapsedTime();
80     std::ofstream os;
81     std::chrono::time_point<std::chrono::high_resolution_clock> mStart;
82 };
83 
84 // Controls logging of a single line.
85 class LoggerStream {
86    public:
LoggerStream(bool abortAfterLog)87     LoggerStream(bool abortAfterLog) : mAbortAfterLog(abortAfterLog) {}
~LoggerStream()88     ~LoggerStream() {
89         Logger::get()->log(ss.str() + '\n');
90         if (mAbortAfterLog) {
91             if (LoggerStream::mWriteAbortMessageToSystemLog) {
92                 __android_log_print(ANDROID_LOG_FATAL, mLogTag.c_str(), "%s", ss.str().c_str());
93             } else {
94                 std::cout << ss.str() << std::endl;
95             }
96             abort();
97         }
98     }
99 
100     template <typename T>
101     LoggerStream& operator<<(const T& str) {
102         ss << str;
103         return *this;
104     }
105 
writeAbortMessageToSystemLog(const std::string & logTag)106     static void writeAbortMessageToSystemLog(const std::string& logTag) {
107         LoggerStream::mWriteAbortMessageToSystemLog = true;
108         LoggerStream::mLogTag = logTag;
109     }
110 
111    private:
112     LoggerStream(const LoggerStream&) = delete;
113     LoggerStream& operator=(const LoggerStream&) = delete;
114     std::stringstream ss;
115     bool mAbortAfterLog;
116 
117     static bool mWriteAbortMessageToSystemLog;
118     static std::string mLogTag;
119 };
120 
121 template <typename T>
toString(const T & obj)122 inline std::string toString(const T& obj) {
123     return std::to_string(obj);
124 }
125 
126 template <typename T>
joinStr(const std::string & joint,const std::vector<T> & items)127 inline std::string joinStr(const std::string& joint, const std::vector<T>& items) {
128     std::stringstream ss;
129     for (uint32_t i = 0; i < items.size(); i++) {
130         if (i == 0) {
131             ss << toString(items[i]);
132         } else {
133             ss << joint << toString(items[i]);
134         }
135     }
136     return ss.str();
137 }
138 
139 template <typename T, class Function>
joinStr(const std::string & joint,const std::vector<T> & items,Function str)140 inline std::string joinStr(const std::string& joint, const std::vector<T>& items, Function str) {
141     std::stringstream ss;
142     for (uint32_t i = 0; i < items.size(); i++) {
143         if (i != 0) ss << joint;
144         ss << str(items[i]);
145     }
146     return ss.str();
147 }
148 
149 template <typename T>
joinStr(const std::string & joint,int limit,const std::vector<T> & items)150 inline std::string joinStr(const std::string& joint, int limit, const std::vector<T>& items) {
151     if (items.size() > static_cast<size_t>(limit)) {
152         std::vector<T> topMax(items.begin(), items.begin() + limit);
153         return joinStr(joint, topMax) + ", (" + toString(items.size() - limit) + " ommited), " +
154                toString(items.back());
155     } else {
156         return joinStr(joint, items);
157     }
158 }
159 
160 static const char* kLifeTimeNames[6] = {
161         "TEMPORARY_VARIABLE", "SUBGRAPH_INPUT",     "SUBGRAPH_OUTPUT",
162         "CONSTANT_COPY",      "CONSTANT_REFERENCE", "NO_VALUE",
163 };
164 
165 static const bool kScalarDataType[]{
166         true,   // ANEURALNETWORKS_FLOAT32
167         true,   // ANEURALNETWORKS_INT32
168         true,   // ANEURALNETWORKS_UINT32
169         false,  // ANEURALNETWORKS_TENSOR_FLOAT32
170         false,  // ANEURALNETWORKS_TENSOR_INT32
171         false,  // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
172         true,   // ANEURALNETWORKS_BOOL
173         false,  // ANEURALNETWORKS_TENSOR_QUANT16_SYMM
174         false,  // ANEURALNETWORKS_TENSOR_FLOAT16
175         false,  // ANEURALNETWORKS_TENSOR_BOOL8
176         true,   // ANEURALNETWORKS_FLOAT16
177         false,  // ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
178         false,  // ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
179         false,  // ANEURALNETWORKS_TENSOR_QUANT8_SYMM
180         false,  // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED
181 };
182 
183 static const uint32_t kSizeOfDataType[]{
184         4,  // ANEURALNETWORKS_FLOAT32
185         4,  // ANEURALNETWORKS_INT32
186         4,  // ANEURALNETWORKS_UINT32
187         4,  // ANEURALNETWORKS_TENSOR_FLOAT32
188         4,  // ANEURALNETWORKS_TENSOR_INT32
189         1,  // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
190         1,  // ANEURALNETWORKS_BOOL
191         2,  // ANEURALNETWORKS_TENSOR_QUANT16_SYMM
192         2,  // ANEURALNETWORKS_TENSOR_FLOAT16
193         1,  // ANEURALNETWORKS_TENSOR_BOOL8
194         2,  // ANEURALNETWORKS_FLOAT16
195         1,  // ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
196         2,  // ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
197         1,  // ANEURALNETWORKS_TENSOR_QUANT8_SYMM
198         1,  // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED
199 };
200 
201 template <>
202 inline std::string toString<RandomVariableType>(const RandomVariableType& type) {
203     static const std::string typeNames[] = {"FREE", "CONST", "OP"};
204     return typeNames[static_cast<int>(type)];
205 }
206 
alignedString(std::string str,int width)207 inline std::string alignedString(std::string str, int width) {
208     str.push_back(':');
209     str.resize(width + 1, ' ');
210     return str;
211 }
212 
213 template <>
214 inline std::string toString<RandomVariableRange>(const RandomVariableRange& range) {
215     return "[" + joinStr(", ", 20, range.getChoices()) + "]";
216 }
217 
218 template <>
219 inline std::string toString<RandomOperandType>(const RandomOperandType& type) {
220     static const std::string typeNames[] = {"Input", "Output", "Internal", "Parameter", "No Value"};
221     return typeNames[static_cast<int>(type)];
222 }
223 
224 template <>
225 inline std::string toString<RandomVariableNode>(const RandomVariableNode& var) {
226     std::stringstream ss;
227     ss << "var" << var->index << " = ";
228     switch (var->type) {
229         case RandomVariableType::FREE:
230             ss << "FREE " << toString(var->range);
231             break;
232         case RandomVariableType::CONST:
233             ss << "CONST " << toString(var->value);
234             break;
235         case RandomVariableType::OP:
236             ss << "var" << var->parent1->index << " " << var->op->getName();
237             if (var->parent2 != nullptr) ss << " var" << var->parent2->index;
238             ss << ", " << toString(var->range);
239             break;
240         default:
241             NN_FUZZER_CHECK(false);
242     }
243     ss << ", timestamp = " << var->timestamp;
244     return ss.str();
245 }
246 
247 template <>
248 inline std::string toString<RandomVariable>(const RandomVariable& var) {
249     return "var" + std::to_string(var.get()->index);
250 }
251 
252 template <>
253 inline std::string toString<RandomOperand>(const RandomOperand& op) {
254     return toString(op.type) + ", dimension = [" +
255            joinStr(", ", op.dimensions,
256                    [](const RandomVariable& var) { return std::to_string(var.getValue()); }) +
257            "], scale = " + toString(op.scale) + " , zero_point = " + toString(op.zeroPoint);
258 }
259 
260 // This class is a workaround for two issues our code relies on:
261 // 1. sizeof(bool) is implementation defined.
262 // 2. vector<bool> does not allow direct pointer access via the data() method.
263 class bool8 {
264    public:
bool8()265     bool8() : mValue() {}
bool8(bool value)266     /* implicit */ bool8(bool value) : mValue(value) {}
267     inline operator bool() const { return mValue != 0; }
268 
269    private:
270     uint8_t mValue;
271 };
272 static_assert(sizeof(bool8) == 1, "size of bool8 must be 8 bits");
273 
274 struct RandomNumberGenerator {
275     static std::mt19937 generator;
276 };
277 
getBernoulli(double p)278 inline bool getBernoulli(double p) {
279     std::bernoulli_distribution dis(p);
280     return dis(RandomNumberGenerator::generator);
281 }
282 
283 template <typename T>
284 inline constexpr bool nnIsFloat = std::is_floating_point_v<T> || std::is_same_v<T, _Float16>;
285 
286 // getUniform for floating point values operates on a open interval (lower, upper).
287 // This is important for generating a scale that is greater than but not equal to a lower bound.
288 template <typename T>
getUniform(T lower,T upper)289 inline std::enable_if_t<nnIsFloat<T>, T> getUniform(T lower, T upper) {
290     float nextLower = std::nextafter(static_cast<float>(lower), std::numeric_limits<float>::max());
291     std::uniform_real_distribution<float> dis(nextLower, upper);
292     return dis(RandomNumberGenerator::generator);
293 }
294 template <typename T>
getUniformNonZero(T lower,T upper,T zeroPoint)295 inline std::enable_if_t<nnIsFloat<T>, T> getUniformNonZero(T lower, T upper, T zeroPoint) {
296     if (upper >= zeroPoint) {
297         upper = std::nextafter(static_cast<float>(upper), std::numeric_limits<float>::min());
298     }
299     std::uniform_real_distribution<float> dis(lower, upper);
300     const float value = dis(RandomNumberGenerator::generator);
301     return value >= zeroPoint ? std::nextafter(value, std::numeric_limits<float>::max()) : value;
302 }
303 
304 // getUniform for integers operates on a closed interval [lower, upper].
305 // This is important that 255 should be included as a valid candidate for QUANT8_ASYMM values.
306 template <typename T>
getUniform(T lower,T upper)307 inline std::enable_if_t<std::is_integral_v<T>, T> getUniform(T lower, T upper) {
308     std::uniform_int_distribution<T> dis(lower, upper);
309     return dis(RandomNumberGenerator::generator);
310 }
311 template <typename T>
getUniformNonZero(T lower,T upper,T zeroPoint)312 inline std::enable_if_t<std::is_integral_v<T>, T> getUniformNonZero(T lower, T upper, T zeroPoint) {
313     if (upper >= zeroPoint) upper--;
314     std::uniform_int_distribution<T> dis(lower, upper);
315     const T value = dis(RandomNumberGenerator::generator);
316     return value >= zeroPoint ? value + 1 : value;
317 }
318 
319 template <typename T>
getRandomChoice(const std::vector<T> & choices)320 inline const T& getRandomChoice(const std::vector<T>& choices) {
321     NN_FUZZER_CHECK(!choices.empty()) << "Empty choices!";
322     std::uniform_int_distribution<size_t> dis(0, choices.size() - 1);
323     size_t i = dis(RandomNumberGenerator::generator);
324     return choices[i];
325 }
326 
327 template <typename T>
randomShuffle(std::vector<T> * vec)328 inline void randomShuffle(std::vector<T>* vec) {
329     std::shuffle(vec->begin(), vec->end(), RandomNumberGenerator::generator);
330 }
331 
332 }  // namespace fuzzing_test
333 }  // namespace nn
334 }  // namespace android
335 
336 #endif  // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H
337