forked from google/fuzztest
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathknobs.h
203 lines (183 loc) · 7.51 KB
/
knobs.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Copyright 2022 The Centipede Authors.
//
// 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
//
// https://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.
#ifndef THIRD_PARTY_CENTIPEDE_KNOBS_H_
#define THIRD_PARTY_CENTIPEDE_KNOBS_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string_view>
#include "absl/types/span.h"
#include "./centipede/defs.h"
namespace centipede {
// Opaque ID object to be used by Knobs.
// Supported usage:
// * Create a new KnobId global object via Knobs::New().
// * Compare two KnobIds for equality.
// * Pass to Knobs' member functions.
class KnobId {
public:
bool operator==(const KnobId& other) const { return id_ == other.id_; }
private:
friend class Knobs;
FRIEND_TEST(Knobs, Choose);
KnobId(size_t id) : id_(id) {}
KnobId() = default;
size_t id() const { return id_; }
size_t id_ = {};
};
// Knobs (will) control all randomized choices made by the fuzzing engine.
//
// The intent is to find optimal values for knobs using machine learning.
//
// Examples of the choices that the engine can make using knobs:
// * Choosing whether to add a given element to the corpus based on what
// features it has, its size, its resource consumption, etc.
// * Choosing a corpus element to mutate, or an element pair to cross-over.
// E.g. make the choice depending on the features associated with elements,
// their sizes, etc.
// * Choosing how to mutate.
// E.g. whether to insert, overwrite, swap, etc., or whether to cross-over.
//
// `Knobs` is effectively a fixed-size array of bytes with named elements.
// The engine loads this array at startup or uses a default value zero.
// The engine may also pass Knobs to a custom mutator that supports it.
//
// Each knob has its own interpretation.
// Some knobs are probability weights, with `0` meaning "never" or "rare"
// and 255 meaning "frequently".
// Some knobs have a meaning in combination with other knobs, e.g.
// when choosing one of N strategies, N knobs will be used as weights.
// Some knobs may mean the number of repetitions of a certain process.
//
// A knob value is accessed via a KnobId.
// KnobIds are created by Knobs::New() as file-scope globals.
// The allocation of KnobIds is stable between the executions of the engine,
// but will change when the engine changes in some significant way
// (e.g. new knobs are added/removed or linking order changes).
// I.e. the optimal knob values will need to be re-learned after major changes
// in the engine.
// This way knobs can be created locally in every source file, w/o having a
// centralized knob repository.
//
// A KnobId can be used to access a knob value: Knobs::Value().
// A set of KnobIds can be used to choose from several choices: Knobs::Choose().
// One KnobID can be used to choose from two choices: Knobs::GenerateBool().
//
// TODO(kcc): figure out how to share knobs with other processes/binaries,
// such as custom mutators.
class Knobs {
public:
// Total number of knobs. Keep it small-ish for now.
static constexpr size_t kNumKnobs = 32;
using value_type = uint8_t;
using signed_value_type = int8_t;
// Creates and returns a new KnobId and associates a `knob_name` with it.
// Must be called at the process startup (assign the result to a global):
// static const KnobId knob_weight_of_foo = Knobs::NewId("weight_of_foo");
// Will trap if runs out of IDs.
static KnobId NewId(std::string_view knob_name);
// Returns the name associated with `knob_id`.
static std::string_view Name(KnobId knob_id) {
return knob_names_[knob_id.id()];
}
// Sets all knobs to the same value `value`.
void Set(value_type value) {
for (auto& knob : knobs_) {
knob = value;
}
}
// Sets the knobs to values from `values`. If `values.size() < kNumKnobs`,
// only the first `values.size()` values will be set.
void Set(absl::Span<const value_type> values) {
size_t n = std::min(kNumKnobs, values.size());
for (size_t i = 0; i < n; ++i) {
knobs_[i] = values[i];
}
}
// Returns the value associated with `knob_id`.
value_type Value(KnobId knob_id) const {
if (knob_id.id() >= kNumKnobs) __builtin_trap();
return knobs_[knob_id.id()];
}
// Returns the signed value associated with `knob_id`.
signed_value_type SignedValue(KnobId knob_id) const { return Value(knob_id); }
// Calls `callback(Name, Value)` for every KnobId created by NewId().
void ForEachKnob(
const std::function<void(std::string_view, Knobs::value_type)>& callback)
const {
for (size_t i = 0; i < next_id_; ++i) {
callback(Name(i), Value(i));
}
}
// Returns one of the `choices`.
// `knob_ids` and `choices` must have the same size and be non-empty.
// Uses knob values associated with knob_ids as probability weights for
// respective choices.
// E.g. if knobs.Value(knobA) == 100 and knobs.Value(knobB) == 10, then
// Choose<...>({knobA, knobB}, {A, B}, rng()) is approximately 10x more likely
// to return A than B.
//
// If all knob values are zero, behaves as if they were all 1.
//
// `random` is a random number derived from an RNG.
// TODO(kcc): consider making this more similar to GenerateBool() and
// requiring 1 knob fewer than choices.size().
template <typename T>
T Choose(absl::Span<const KnobId> knob_ids, absl::Span<const T> choices,
uint64_t random) const {
if (choices.empty()) __builtin_trap();
if (knob_ids.size() != choices.size()) __builtin_trap();
size_t sum = 0;
for (auto knob_id : knob_ids) {
sum += Value(knob_id);
}
if (sum == 0) return choices[random % choices.size()];
random %= sum;
size_t partial_sum = 0;
size_t idx = 0;
for (auto knob_id : knob_ids) {
partial_sum += Value(knob_id);
if (partial_sum > random) return choices[idx];
++idx;
}
__builtin_unreachable();
}
// Chooses between two strategies, i.e. returns true or false.
// Treats the value of the knob associated with `knob_id` as signed integer.
// If knob == -128, returns false. If knob == 127 returns true.
// For other values, returns randomly true of false, with higher probability
// of true for higher values of knob.
// If knob == 0, returns true with a ~ 50% chance.
// `random` is a random number used to produce random choice.
bool GenerateBool(KnobId knob_id, uint64_t random) const {
signed_value_type signed_value = SignedValue(knob_id); // in [-128,127]
signed_value_type rand = random % 255 - 127; // in [-127,127]
// signed_value == 127 => always true.
// signed_value == -128 => always false.
// signed_value == 0 => true ~ half the time.
return signed_value >= rand;
}
// Variant of Choose() where the choices are KnobIds themselves.
// Returns one of the `choices` based on the respective knobs.
KnobId Choose(absl::Span<const KnobId> choices, uint64_t random) const {
return Choose<KnobId>(choices, choices, random);
}
private:
static size_t next_id_;
static std::string_view knob_names_[kNumKnobs];
value_type knobs_[kNumKnobs] = {};
};
} // namespace centipede
#endif // THIRD_PARTY_CENTIPEDE_KNOBS_H_