]> git.ipfire.org Git - thirdparty/pdns.git/blame - CODING_GUIDELINES.md
Add a coding guidelines document to the public repository
[thirdparty/pdns.git] / CODING_GUIDELINES.md
CommitLineData
8f7dc5e9
RG
1Coding guidelines for contributing to PowerDNS
2----------------------------------------------
3
4Thank you for you interest to contribute to the PowerDNS project. This document describes the general coding guidelines to keep in mind when contributing code to our code base. It does assume that you have already read the contributing document at [CONTRIBUTING.md](https://github.com/PowerDNS/pdns/blob/master/CONTRIBUTING.md).
5
6# High-level guidelines
7
8* Although the codebase does not consistently have them, [docblock](https://www.doxygen.nl/manual/docblocks.html)s on functions and classes are appreciated.
9* Never hesitate to write comments on anything that might not be immediately clear just from reading the code.
10* When adding whole new things, consider putting them in a `pdns::X` namespace. Look for `namespace pdns` in the codebase for examples.
11
12# Memory handling
13
14The memory model in C++, inherited from the C era, is very powerful but also very error-prone. Several features are available in modern (C++11) C++ to make it possible to avoid most of the pitfalls, while conserving the same level of performance.
15
16Most of the issues related to memory allocation (memory leaks, use-after-free) can be solved by using it via standard containers, or taking advantage of RAII and smart pointers, which take care of destructing the object when it's not used anymore.
17
18## Stack-based memory allocation
19
20Default allocations, when declaring a variable local to a function for example, is done on the stack instead of doing a dynamic allocation on the heap. Allocating objects on the stack is faster, especially in threaded programs, and provides the benefit that objects are automatically destroyed when the function is exited.
21
22One caveat is that the programmer needs to be wary about the size of the object in order not to exceed the space available on the stack, which would corrupt other objects in memory and could lead to a crash, or even execution of arbitrary code. This is especially true in the recursor which uses a custom stack-switching in user-space mechanism and thus has a reduced stack size.
23
24### Variable-Length Arrays
25
26In order to avoid smashing the stack, a special care should be taken to limit the depth of function calls that can grow quickly with recursion, for example. A second common source of smash stacking is the use of Variable-Length Arrays, whose size is determined at runtime and is therefore very hard to predict. The C++ language doesn't support VLAs but a lot of compilers inherit such a support from C99, so it's possible to use them by mistake. PowerDNS strictly forbids the use of VLAs, as does the Linux kernel, and enforce that with the `-Werror=vla` compiler flag.
27
28### C-style arrays
29
30While you might still find some in the existing code base, we are actively trying to get rid of C-style arrays like this one:
31
32```C++
33somestruct buffer[12];
34auto bufferSize = sizeof(buffer) / sizeof(*buffer);
35auto& firstElement = buffer[0];
36```
37
38It is immediately obvious that computing the actual number of elements is error-prone, as `sizeof()` does not return the number of elements but the total memory space used by the array. An other obvious issue is that accesses to the array are not bound-checked. These are not the only drawbacks of these arrays, but are bad enough already to justify getting rid of them.
39
40The modern C++ way is to use `std::array`s:
41
42```C++
43std::array<somestruct, 12> buffer;
44auto bufferSize = buffer.size();
45auto& firstElement = buffer.at(0);
46```
47
48### Alloca
49
50The use of `alloca()` is forbidden in the code base as it is much too easy to smash the stack.
51
52## RAII
53
54Resource acquisition is initialization (RAII) is one of the fundamental concept in C++. Resources are allocated during the construction of an object and destroyed when the object is itself destructed. It means that if an object is correctly designed, the resource associated to it can not survive its lifetime. Since objects that are allocated on the stack (local variables in a function, for example) are automatically destroyed when a function is exited, be it by reaching the last line, calling return or throwing an exception, it makes it possible to ensure that resources are always properly destroyed by wrapping them into an object.
55
56We describe the use of smart pointers, containers and other wrappers to that mean below, but first a few words of caution. Resources stored in a object are only tied to this object if the constructor finished properly. If an exception is raised in the constructor body, the object is not created and therefore the destructor will not get called. This means that if the object has non-object members holding resources, like naked file descriptors or naked pointers, they need to be explicitly released before raising the exception, otherwise they are lost.
57
58```C++
59class BadFileDescriptionWrapper
60{
61 BadFileDescriptionWrapper()
62 {
63 d_fd = open(...);
64 if (something) {
65 throw std::runtime_error(...);
66 }
67 ...
68 }
69 ~BadFileDescriptionWrapper()
70 {
71 if (d_fd > 0) {
72 close(d_fd);
73 d_fd = -1;
74 }
75 }
76 int getHandle() const
77 {
78 return d_fd;
79 }
80private:
81 int d_fd{-1};
82};
83```
84
85The use of smart pointers can be a solution to most resources leakage, but otherwise the only way is to be careful about exceptions in constructors:
86
87```C++
88BadFileDescriptionWrapper()
89{
90 d_fd = open(...);
91 if (something) {
92 close(d_fd);
93 throw std::runtime_error(...);
94 }
95 ...
96}
97```
98
99## Smart pointers
100
101There is almost no good reason not to use a smart pointer when doing dynamic memory allocation. Smart pointers will keep track of whether the dynamically allocated object is still used, and destroy when the last user goes away.
102
103Using naked pointers quickly results in security issues, going from memory leaks to arbitrary code execution. Examples of such issues can be found in the following PowerDNS security advisories:
104
105* https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2017-07.html
106* https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-04.html
107
108Most allocations should be wrapped in a `std::unique_pointer`, using `make_unique`. There can only be one owner at a given time, as opposed to shared pointers, but the ownership can be passed along using `std::move()` if needed.
109
110If the dynamically allocated object needs to be referenced in several places, the use of a `std::shared_pointer` is advised instead, via `std::make_shared`.
111
112The use of the `make_*` methods have three advantages:
113
114* they result in a single allocation for `shared_pointer`s, instead of two otherwise ;
115* they avoid duplicating the type name twice ;
116* they prevent a possible issue if an exception is raised with temporaries.
117
118They also make is easier to spot naked pointers by looking for "new" and "delete" throughout the code :)
119
120Please note however that while unique pointers are as cheap as naked pointers, shared pointers are much more expensive. That's because they need to use atomic operations to update their internal counters, so making a copy of a shared pointer is expensive. Passing one by reference is cheap, however.
121
122### Shared pointers
123
124An important thing to be aware of with shared pointers is that taking a new copy of a shared pointer or releasing, thus updating its internal reference counter, is atomic and therefore thread-safe. Altering the content of the object pointed to is not, though, and is subject to the usual locking methods. The often misunderstood part is that updating the target of the shared pointer is not thread-safe. Basically, you can copy the shared pointer from multiple threads at once, and then each thread can assign a new target to its own copy safely, like that:
125
126```C++
127auto ptr = std::make_shared<int>(4);
128for (auto idx = 0; idx < 10 ; idx++){
129 std::thread([ptr]{ auto copy = ptr; }).detach(); //ok, only mutates the control block
130}
131```
132
133But there is a race if one thread update the exact same smart pointer that another thread is trying to read:
134
135```c++
136auto ptr = std::make_shared<int>(4);
137
138std::thread threadA([&ptr]{
139 ptr = std::make_shared<int>(10);
140});
141std::thread threadB([&ptr]{
142 ptr = std::make_shared<int>(20);
143});
144```
145
146That unfortunately means that we still need some locking with shared pointers. C++11 defines atomic compare/exchange operations for `std::shared_ptr`, but they are implemented in `libstdc++` by global mutexes and are therefore not lock-free.
147
148### Wrapping C pointers
149
150Smart pointers can also be used to wrap C-pointers, such as `FILE*` pointers:
151
152```c++
153auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(certificateFile.c_str(), "r"), fclose);
154```
155
156It also works with types from external C libraries, like OpenSSL:
157
158```c++
159auto cert = std::unique_ptr<X509, decltype(&X509_free)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
160```
161
162Unfortunately there are a few cases where smart pointers cannot be used. In the PowerDNS products, these cases have been mostly reduced to a few select classes, like the `pdns::channel` ones, that are used to pass pointers to a different thread by writing them to a pipe, as is done for example by the queries distributors of the auth and the rec.
163
164When it happens, special care should be taken to:
165
166* make sure that every exit point frees the allocated memory (early return, goto, exceptions..) ;
167* set the pointer to `nullptr` right after the deallocation, so we can't use it again (use-after-free) ;
168* do not mix `malloc` with `delete`, `new` with `free` (destructors are not run, at the very least) ;
169* do not mix array allocations (`new[]`) with a non-array `delete` (vs `delete[]`).
170
171## Pointer arithmetic
172
173It is very common to use pointer arithmetic to calculate a position in a buffer, or to test whether a given offset is outside of a given buffer. Unfortunately it is quite easy to trigger undefined behaviour when doing so, as the C++ standard does not allow pointer arithmetic pointing inside an object, except for arrays where it is also permitted to point one element past the end. Still that undefined behaviour is mostly harmless, but it might lead to real issue on some platforms.
174
175One such example occurred in dnsdist: https://dnsdist.org/security-advisories/powerdns-advisory-for-dnsdist-2017-01.html
176
177In that case, a pointer was set to the start of a buffer plus a given length, to see whether the result go past another pointer that was set to the end of the buffer. Unfortunately, if the start of the buffer is at a very high virtual address, the result of the addition might overflow and wrap around, causing the check to become true and leading to either a crash or the reading of unrelated memory. While very unlikely on a 64 bits platform, it could happen on 16 or 32 bits platform.
178
179This kind of issue is best avoided by the use of container to prevent the need of pointer arithmetic, or by very careful to only add checked offsets to a pointer.
180
181### Containers
182
183The use of containers like `vector`, `map` or `set` has several advantages in term of security:
184
185* memory allocations are handled by the container itself ;
186* it prevents a disconnect between the actual size and the variable tracking that size ;
187* it provides safe (and fast) operations like comparisons, iterators, etc..
188
189One issue that could have been prevented by the use of a container can be found in the following advisory: https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-09.html
190
191The use of a container and its corresponding `at()` operator would have prevented an out-of-bounds read since calling `at()` on an invalid offset results in an exception being raised. The cost of using `at()` is negligible for most use cases, and can be avoided by using the `[]` operator in the rate case when the cost can't be afforded. Note that several Linux distributions now build with `-Wp,-D_GLIBCXX_ASSERTIONS` enabled by default, which turns on cheap range checks for C++ arrays, vectors, and strings anyway.
192
193Regarding performance, it is advised to `reserve()` the needed size in advance when a rough estimate is known to avoid reallocations and copies. Resizing in advance is not advised, though, as it makes it harder to know what is exactly in the container in case of early returns or exceptions.
194
195In C++11, move operations make it possible to cheaply get the content of a container into a different variable if needed.
196
197The need to pass a subset of a container without copying it often leads to passing a pointer to an array of chars along with a size. Introduced in C++14 but already available in PowerDNS via boost (see views.hh), views provides a nice way to borrow the content of a container to pass it to a function, without any copy or dynamic memory allocation.
198
199The basic `string_view` class provides that feature for a container of chars, but the same feature exists for other types, like `uint8_t`.
200
201# Threads and concurrency
202
203All of our products use threading to be able to take advantage of the increasing number of cores on modern CPUs. That inevitably leads to the question of how to synchronise data accesses between threads. Most objects, like containers, cannot be accessed from more than one thread at once. Even `const` methods on containers might not be thread-safe. For example getting the `size()` of a container might not be thread-safe if a different thread might be writing to the container. Some functions might also not be thread-safe, for example if they have a static non-const variable.
204
205We currently use three solutions, depending on the use-case. The first one is used when we only need to share some kind of counters or gauges, and involves the use of `std::atomic` which allows atomic operations to be performed from different threads without locking. More on that later. The second one is the "share nothing" approach, where each thread has its own data (using `thread_local`, for example), avoiding the need to data synchronization. When a thread needs to communicate with another one, it might use an pdns::channel to pass a pointer to that second thread. That works quite well but sometimes sharing data is much more efficient than the alternative.
206
207For these cases, we use the classic locking approach, using either a simple mutex or read-write lock, depending on the use case.
208
209## locks
210
211Locks allow a thread of execution to ensure that no other will try to access the code path or data they protect at the same time.
212
213There are a few pitfalls to avoid then using locks:
214
215* avoiding to release the lock, which can be avoided by wrappers like `std::lock_guard`, `std::unique_lock` and our own wrappers: look for `LockGuarded`, `SharedLockGuarded` in lock.hh ;
216* high contention, where threads are blocked for a long time while waiting to acquire a lock. This can be solved by carefully examining the portion of code that really needs to hold the lock, making the critical path faster, or by using sharding which basically divide the data protected by the lock in several blocks, each one of them protected by its own lock ;
217* starvation, which occurs for example when thread 1 acquires lock 1 and wants to acquire lock 2, which is already owned by thread 2, itself currently waiting to acquire lock 1. This can be avoided by a better design of the locking mechanism, and assuring that locks are always acquired in the same order if more than one lock is needed.
218
219There are more than one type of locks:
220
221* spinlock are very fast but are busy-waiting, meaning that they don't pause but repetitively try to get hold of the lock, using 100% of one core doing so unless preempted by the OS. So they are only suited for locks that are almost never contented ;
222* a mutex is a very simple lock. In most implementations it's a very fast lock, implemented in user-space on recent Linux kernels and glibc ;
223* a read-write lock allows several threads to acquire it in read mode, but only one thread can acquire it in write mode. This is suited when most accesses are read-only and writes are rare and don't take too long. Otherwise a mutex might actually be faster ;
224
225One quick word about condition variables, that allows a thread to notify one or more threads waiting for a condition to happen. A thread should acquire a mutex using a `std::unique_lock` and call the `wait()` method of the condition variable. This is a very useful mechanism but one must be careful about two things:
226
227* the producer thread can either wake only one thread or all threads waiting on the condition variable. Waking up several threads if only one has something to do (known as a "thundering herd") is bad practice, but there are some cases where it makes sense ;
228* a consumer might be waken up spuriously, which can be avoided by passing a predicate (which can be as simple as a small lambda function) to `wait()`.
229
230Our wrappers, `LockGuarded`, `SharedLockGuarded` in lock.hh, should always be preferred over other solutions. They provide a way to wrap any data structure as protected by a lock (mutex or shared mutex), while making it immediately clear which data is protected by that lock, and preventing any access to the data without holding the lock.
231
232For example, to protect a set of integers with a simple mutex:
233
234```c++
235LockGuarded<std::set<int>> d_data;
236```
237
238or with a shared mutex instead:
239
240```c+++
241SharedLockGuarded<std::set<int>> d_data;
242```
243
244Then the only way to access the data is to call the `lock()`, `read_only_lock()` or `try_lock()` methods for the simple case, or the `read_lock()`, `write_lock()`, `try_read_lock()` or `try_write_lock()` for the shared one.
245Doing so will return a "holder" object, which provides access to the protected data, checking that the lock has really been acquired if needed (`try_` cases). The data might be read-only if `read_lock()`, `try_read_lock()` or `read_only_lock()` was called. Access is provided by dereferencing the holder object via `*` or `->`, allowing a quick-access syntax:
246
247```c+++
248return d_data.lock()->size();
249```
250
251Or when the lock needs to be kept for a bit longer:
252
253```c++
254{
255 auto data = d_data.lock();
256 data->clear();
257 data->insert(42);
258}
259```
260
261## atomic
262
263`std::atomic` provides a nice way to share a counter or gauge between threads without the need for locking. This is done by implementing operations like reading, increasing, decreasing or writing a value in an atomic way, using memory barriers, making sure that the value can't be updated from a different core during the operation. The default mode uses a sequentially consistent ordering memory model, which is quite expensive since it requires a full memory fence on all multi-core systems. A relaxed model can be used for certain very specific operations, but the default model has the advantage of being safe in all situations.
264
265## per-thread counters
266
267For generic per-thread counters, we have a class in tcounters.hh that should provide better performances by allowing each thread to independently update its own counter, the costly operation only happening when the counter needs to be read by one thread gathering metrics from all threads.
268
269# Dealing with untrusted data
270
271As a rule of thumb, any data received from outside the process should be considered as untrusted. That means data received on a socket, loaded from a file, retrieved from a database, etc.. Data received from an internal pipe might be excluded from that rule.
272
273Untrusted data should never be trusted to adhere to the expected format or specifications, and a strict checking of boundaries should be performed. It means for example that, after reading the length for a field inside the data, whether that length does not exceed the total length of the data should be checked. In the same way, if we expect a numerical type we should check whether it matches one we expect and understand.
274
275Anything unexpected should stop the processing and lead to the discarding of the complete data set. If a smaller data set can be safely discarded, and it is more important to load an incomplete set than to assure the integrity of the complete data set, only the faulty data set can be discarded instead.
276alignment issues
277
278When structured, binary data is received from the network or read from a file, it might be tempting to map it to an existing structure directly to make the parsing easier. But one must be careful about alignment issues on some architectures:
279
280```c++
281struct my_struct {
282 uint32_t foo;
283 uint32_t bar;
284};
285```
286
287It might be tempting to directly cast the received data:
288
289```c++
290void func(char* data, size_t offset, size_t length) {
291 // bounds check left out!
292 const struct my_struct* tmp = reinterpret_cast<const struct my_struct*>(data + offset);
293 ...
294}
295```
296
297Unfortunately this leads to undefined behaviour because the offset might not be aligned with the alignment requirement of the struct. One solution is to do a copy:
298
299```c++
300void func(char* data, size_t offset, size_t length) {
301 // bounds check left out!
302 struct my_struct tmp;
303 memcpy(&tmp, data + offset, sizeof(tmp));
304 /* ... */
305}
306```
307
308## unsigned vs signed
309
310Signed integer might overflow, and the resulting value is unpredictable, as this is an undefined behaviour. That means that this code result in an unpredictable value:
311
312```c++
313int8_t a = std::numeric_limits<int8_t>::max();
314a++;
315```
316
317One such example led to https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2006-01.html
318
319It would be necessary to check that the value can't overflow first. Another possibility would be to instruct the compiler to treat signed overflow as it does for unsigned values, by wrapping. This can be done with `-fwrapv` with g++.
320
321An operation on an unsigned integer will never result in an overflow, because the value will simply wrap around. This might still result in an unexpected value, possibly bypassing a critical check:
322
323```c++
324void parse_untrusted_data(uint8_t* data, uint16_t length)
325{
326 /* parse a record, first two bytes are the size of the record data, second two bytes are the type of the record */
327 if (length < 4) {
328 return;
329 }
330 /* read the first two bytes which hold the length of the next record */
331 uint16_t recordLen = data[0] * 256 + data[1];
332 /* let's assume that recordLen is equal to 65535 */
333 uint16_t totalRecordLen = /* size of the type */ sizeof(uint16_t) + recordLen; // <-- this results in a wrapped value of 65535 + 2 = 65537 = 1
334 if (totalRecordLen > length) {
335 return;
336 }
337 /* ... */
338}
339```
340
341A valid version to prevent the overflow:
342
343```c++
344void parse_untrusted_data(uint8_t* data, uint16_t length)
345{
346 /* parse a record, first two bytes are the size of the record data, second two bytes are the type of the record */
347 if (length < 4) {
348 return;
349 }
350 /* read the first two bytes which hold the length of the next record */
351 uint16_t recordLen = data[0] * 256 + data[1];
352 if (recordLen > length || (length - recordLen) < sizeof(uint16_t)) {
353 return;
354 }
355 /* ... */
356}
357```
358
359Converting from unsigned to signed will lose the high order bytes, and should be avoided, or the value should be checked before-hand:
360
361```c++
362uint64_t u = std::numeric_limits<uint64_t>::max();
363int64_t s = static_cast<int64_t>(u); /* Wrong, and the cast eliminates any warning */
364if (u <= std::numeric_limit<int64_t>::max()) {
365 int64_t s = static_cast<int64_t>(u); /* OK */
366}
367```
368
369The `pdns::checked_conv()` function can be used, ensuring that the conversion can safely be done and raising an exception otherwise.
370
371`-Wsign-conversion` can be used to warn about dangerous conversions (disabled by default in g++, and note that a cast disables the warning).
372
373## fuzzing
374
375Fuzzing is a very useful way to test a piece of code that parses untrusted data. Efficient fuzzers are often doing coverage-based fuzzing, where the code that they test have been compiled in a special way to allow the fuzzer to detect which branches are executed and which are not, so that the fuzzer can see the effect of mutating specific byte of the input on the code path.
376
377PowerDNS has a few fuzzing targets that can be used with libFuzzer or AFL in the pdns/ directory, and are built when `--enable-fuzzing-target` is passed to the configure. More information can be found in the [fuzzing/README.md](https://github.com/PowerDNS/pdns/blob/master/fuzzing/README.md) file.
378The existing fuzzing targets are run on the OSS-Fuzz infrastructure for a short time every time a pull request is opened, and for a longer time on the HEAD of the repository.
379
380# Others potential issues
381
382## TOCTOU
383
384The time-of-check time-of-use vulnerability is a very easy mistake to make when dealing with files or directory. The gist of it is that there is a small race condition between the time where a program might check the ownership, permissions or even existence of a file and the time it will actually do something with it. This time might be enough to allow an attacker to create a symbolic link to a critical file at the place of that exact file, for example. Since the program has enough rights to edit this file, this might allow an attacker to trick the program into writing into a completely different file.
385
386This is hard to avoid in all cases, but some mitigations do help:
387
388* opening a file first (handling errors if that fails) then getting the needed metadata via the file descriptor instead of the path (`fstat`, `fchmod`, `fchown`) ;
389* opening with the `O_NOFOLLOW` flag set, so that the operation will fail if the target exists and is a symbolic link ;
390* always creating temporary files via the `mkstemp()` function, which guarantees that the file did not exist before and has been created with the right permissions ;
391* using operations that are guaranteed to be atomic, like renaming a file on the same filesystem (for example in the same directory).
392
393## Secrets
394
395Try very hard not to load sensitive information in memory. And of course don't write to disk!
396
397If you have to:
398
399* use an object that can't be copied by deleting the copy constructors and assignments operators,
400* try to lock the memory so it can't be swapped out to disk, or included in a core dump, via `sodium_malloc()` or `sodium_mlock()`, for example ;
401* wipe the content before releasing the memory, so it won't linger around. Be careful that memset() is very often optimized out by the compiler, so function like `sodium_munlock()`, `explicit_bzero()` or `explicit_memset()` should be used instead.
402
403### Constant-time comparison
404
405Don't compare secret against data using a naive string comparison, as the timing of the operation will leak information against the content of the secret. Ideally, a constant-time comparison should be used instead (see `constantTimeStringEquals()` in the PowerDNS code base) but it's not easy to achieve. One option might be to compute a HMAC of the secret using a key randomly generated at startup, and compare it against a HMAC of the supplied data computed with the same key.
406
407## Virtual destructors
408
409Any class that is expected to be sub-classed should provide a virtual destructor. Not doing so will prevent the destructor of a derived class from being called if the object is held as the base type:
410
411```c++
412class Parent
413{
414 virtual void doVirtualCall();
415};
416
417class Child: public Parent
418{
419 Child()
420 {
421 d_fd = fopen(..);
422 }
423 ~Child()
424 {
425 if (d_fd) {
426 fclose(d_fd);
427 f_fd = nullptr;
428 }
429 }
430 void doVirtualCall() override;
431};
432
433std::vector<Parent> myObjects;
434myObjects.push_back(Child());
435```
436
437Be careful that defining a destructor will prevent the automatic creation of move operations for that class, since they are generated only if these conditions are met:
438
439* no copy operations are declared ;
440* no move operations are declared ;
441* no destructor is declared.
442
443If the Parent class holds data that is costly to copy, it might make sense to declare the move operations explicitly:
444
445```c++
446class Parent
447{
448 Parent(Parent&&) = default;
449 Parent& operator=(Parent&&) = default;
450
451 virtual ~Parent()
452 {
453 }
454
455 virtual void doVirtualCall();
456private:
457 FILE* d_fd{nullptr};
458};
459```
460
461Note that declaring the move operations disables the copy operations, so if they are still needed:
462
463```c++
464class Parent
465{
466 Parent(Parent&&) = default;
467 Parent& operator=(Parent&&) = default;
468
469 Parent(const Parent&) = default;
470 Parent& operator=(const Parent&) = default;
471
472 virtual ~Parent()
473 {
474 }
475
476 virtual void doVirtualCall();
477};
478```
479
480On a related topic, virtual methods should not be called from constructors or destructors. While this is allowed under certain restrictions, it's very hard to know exactly which method (base or derived) will be called, and whether all sub-objects contained in the class would have been correctly constructed at that point.
481
482## Hash collisions
483
484Hashes are a very useful tool, used in `unordered_map` and `unordered_set` among others. They are also used in our caches. An important caveat that developers need to be aware about regarding hashes are that the probability of a collision is often a lot higher than expected. This is well-known as the birthday paradox, the fact that the probability of having to entries colliding is a lot higher than the probability of finding a collision for a specific entry. This means that it is important to verify that the entries are actually identical, and just not that they hash to the same value.
485
486This is especially important when hashing attacker-controlled values, as they can be specially crafted to trigger collisions to cause:
487
488* cache pollution (see https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-06.html) ;
489* denial of service via hash table flooding (in a map, all entries that hash to the same value are often placed into a linked-list, making it possible to cause a linear scanning of entries by making all of them hash to the value).
490
491The first issue can be prevented by comparing the entries and not just the value they hash to. The second one can be used by using some sort of secret when computing the hash so that the result cannot be guessed by the attacker. That can be achieved by using an unpredictable seed for certain hash algorithms, or a secret for some other like `SipHash`.
492
493# Readability tips
494
495Some of these tips are actually enforced by `clang-tidy` nowadays, but it is still useful to keep them in mind.
496
497## Auto
498
499C++11 introduced automatic type deduction, using the auto keyword. In addition to saving the typing of a few more letters, using automatic type deduction prevents nasty surprises if the variable is initialized from another one, or from a function, and the other type is changed to a different one. The code might still compile while now involving a copy or worse.
500Boolean expressions
501
502## Explicit comparisons
503
504* compare numerical values to `0` or `!= 0` explicitly ;
505* compare to `false` explicitly, which is easier to read ;
506* compare to `nullptr` for the same reason.
507
508## Initialization
509
510Use braced initialization for members as often as possible:
511
512* it does forbid narrowing conversions
513* and avoids C++'s "move vexing parse" which is to declare a function instead of calling the default constructor:
514
515```c++
516Object a(); // declares a function named a that returns an object
517```
518
519## nullptr
520
521When representing a pointer, using `nullptr` makes it immediately obvious that we are dealing with a pointer, as opposed to the use of `0`. It also can't be silently taken as an integer, which can happens with `0` but also with `NULL`.
522
523## const-ness
524
525* Mark parameters and variables that should not be modified as `const`. This is especially true for references and pointers that comes from outside the function, but it also makes sense to do it for local variables or parameters passed by value because it might help detect a logic error later.
526* Mark const methods as such (and make them thread-safe)
527* Prefer using `at()` on containers so that no insertion can take place by mistake, and to get bounds checking.
528
529## static
530
531Functions that are only used inside a single file should be marked as `static`, so that:
532
533* the compiler knows that these functions will not be called from a different compilation unit and thus that no symbol needs to be generated, making it more likely for the function to be inlined ;
534* the reader knows that this function is only used there and can be altered without causing an issue somewhere else.
535
536For the same reason, global variables that are only accessed from a single file should be marked static as well.
537
538## Variables
539
540Try to declare variables in the innermost scope possible and avoid uninitialized variables as much as possible. Declare and initialize them when the values needed to initialize them are available.
541
542## Exceptions
543
544Should be reserved to unexpected events (corrupted data, timeouts, ...) and should not be triggered in normal processing.
545
546Don't be afraid of using them, though, as the cost of an exception that is not thrown is usually very small, thanks to the zero-cost exception model. It might still force the compiler to refrain from some optimizations, so it might make sense to avoid them in some very performance-sensitive, narrow code paths.
547
548### Custom exceptions
549
550Exceptions defined by the standards should be used whenever possible, as they already cover a lot of use cases.
551
552If custom exceptions are necessary, to be able to catch them explicitly, they should still derive from `std::exception`, directly or indirectly, so that they can still be caught in a more generic way to prevent the program from terminating. For example, the main connection handling function of a server can catch `std::exception` and just terminate the current connection if an uncaught exception bubbles up.
553
554### Catching exceptions
555
556Catching exceptions should always be done by const reference:
557
558```c+++
559try {
560}
561catch (const std::exception& e) {
562 std::cerr << e.what() <<endl;
563}
564```
565
566Not using a reference would result in the exception object being sliced, meaning that a custom exception derived from `std::exception` would not see its overriding `what()` method called but the one from the base class instead.
567
568## Casts
569
570C-style casts should be avoided, as the compiler does almost no check on the validity of the operation. They are also very hard to spot in a code. C++-style casts can easily be spotted in a code, which makes it easy to review them.
571
572* `const_cast` can be used to remove the const qualifier on a variable. It's usually a bad sign, but sometimes it is needed to call a function that will not modify the variable but lacks the const qualifier, for example.
573* `dynamic_cast` can be used to cast a pointer to a derived class or to a base class, while checking that the operation is valid. If the casted object is not valid for the intended type, a nullptr value will be returned (or a bad_cast exception for references) so the result of the operation should be checked! Note that the RTTI check needed to verify that the casted object is valid has a non-negligible CPU cost. Not checking the return value might lead to remote denial of service by nullptr dereference, as happened with the issue described in this advisory: https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2017-08.html
574* `static_cast` can perform downcast in place of `dynamic_cast`, with none of the cost associated to the check, but can only be done if the cast is known to be valid. It can also do implicit conversion between types (from `ssize_t` to `size_t`, AFTER checking that the value is greater or equal to zero).
575* `reinterpret_cast` is quite dangerous, since it can be used to turn a type into a different one. It can't be be used to remove a const qualifier. When used to reinterpret the content of a buffer it can quickly lead to alignment issues, as described in the [alignment issues] section.
576
577## errno
578
579`errno` is only guaranteed to be set on failing system calls and not set on succeeding system calls. A library call may clobber `errno`, even when it succeeds. Safe practise is:
580
581* Only look at `errno` on failing systems calls or when a library function is documented to set `errno`.
582* Immediately save the value of `errno` after a system call for later decision making in a local variable.