summaryrefslogtreecommitdiff
path: root/COMMON_ERRORS.md
blob: 16c7908aa7fe1fca6e091203b5b8eafa3395443a (plain)
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
# Commonly recurring errors in bmcweb

What follows is a list of common errors that new users to bmcweb tend to make
when operating within its bounds for the first time.  If this is your first time
developing in bmcweb, the maintainers highly recommend reading and understanding
_all_ of common traps before continuing with any development.  Every single one
of the examples below compile without warnings, but are incorrect in
not-always-obvious ways, or impose a pattern that tends to cause hard to find
bugs, or bugs that appear later.  Every one has been submitted to code review
multiple times.

### 1. Directly dereferencing a pointer without checking for validity first
```C++
int myBadMethod(const nlohmann::json& j){
    const int* myPtr = j.get_if<int>();
    return *myPtr;
}
```
This pointer is not guaranteed to be filled, and could be a null dereference.

### 2. String views aren't null terminated
```C++
int getIntFromString(const std::string_view s){
    return std::atoi(s.data());
}
```
This will give the right answer much of the time, but has the possibility to
fail when string\_view is not null terminated.  Use from\_chars instead, which
takes both a pointer and a length

### 3. Not handling input errors
```C++
int getIntFromString(const std::string& s){
    return std::atoi(s.c_str());
}
```
In the case where the string is not representable as an int, this will trigger
undefined behavior at system level.  Code needs to check for validity of the
string, ideally with something like from\_chars, and return the appropriate error
code.

### 4. Walking off the end of a string
```C++
std::string getFilenameFromPath(const std::string& path){
    size_t index = path.find("/");
    if (index != std::string::npos){
        // If the string ends with "/", this will walk off the end of the string.
        return path.substr(pos + 1);
    }
    return "";
}
```

### 5. Using methods that throw (or not handling bad inputs)
```C++
int myBadMethod(nlohmann::json& j){
    return j.get<int>();
}
```
This method throws, and bad inputs will not be handled

Commonly used methods that fall into this pattern:

- std::variant::get
- std::vector::at
- std::map::at
- std::set::at
- std::\<generic container type\>::at
- nlohmann::json::operator!=
- nlohmann::json::operator+=
- nlohmann::json::at
- nlohmann::json::get
- nlohmann::json::get\_ref
- nlohmann::json::get\_to
- std::filesystem::create\_directory
- std::filesystem::rename
- std::filesystem::file\_size
- std::stoi
- std::stol
- std::stoll

#### special/strange case:

nlohmann::json::parse by default throws on failure, but also accepts a optional
argument that causes it to not throw.  Please consult the other examples in the
code for how to handle errors.


#### Special note: Boost
there is a whole class of boost asio functions that provide both a method that
throws on failure, and a method that accepts and returns an error code.  This is
not a complete list, but users should verify in the boost docs when calling into
asio methods, and prefer the one that returns an error code instead of throwing.

- boost::asio::ip::tcp::acceptor::bind();
- boost::asio::ip::tcp::acceptor::cancel();
- boost::asio::ip::tcp::acceptor::close();
- boost::asio::ip::tcp::acceptor::listen();
- boost::asio::ip::address::make\_address();

### 6. Blocking functions

bmcweb uses a single reactor for all operations.  Blocking that reactor for any
amount of time causes all other operations to stop.  The common blocking
functions that tend to be called incorrectly are:

- sleep()
- boost::asio::ip::tcp::socket::read()
- boost::asio::ip::tcp::socket::read\_some()
- boost::asio::ip::tcp::socket::write()
- boost::asio::ip::tcp::socket::write\_some()
- boost::asio::ip::tcp::socket::connect()
- boost::asio::ip::tcp::socket::send()
- boost::asio::ip::tcp::socket::wait()
- boost::asio::steady\_timer::wait()

Note: an exception is made for filesystem/disk IO read and write.  This is
mostly due to not having great abstractions for it that mate well with the async
system, the fact that most filesystem accesses are into tmpfs (and therefore
should be "fast" most of the time) and in general how little the filesystem is
used in practice.

### 7. Lack of locking between subsequent calls
While global data structures are discouraged, they are sometimes required to
store temporary state for operations that require it.  Given the single
threaded nature of bmcweb, they are not required to be explicitly threadsafe,
but they must be always left in a valid state, and checked for other uses
before occupying.

```C++
std::optional<std::string> currentOperation;
void firstCallbackInFlow(){
    currentOperation = "Foo";
}
void secondCallbackInFlow(){
    currentOperation.reset();
}
```

In the above case, the first callback needs a check to ensure that
currentOperation is not already being used.

### 8. Wildcard reference captures in lambdas
```
std::string x; auto mylambda = [&](){
    x = "foo";
}
do_async_read(mylambda)
```

Numerous times, lifetime issues of const references have been injected into
async bmcweb code.  While capturing by reference can be useful, given how
difficult these types of bugs are to triage, bmcweb explicitly requires that all
code captures variables by name explicitly, and calls out each variable being
captured by value or by reference.  The above prototypes would change to
[&x]()... Which makes clear that x is captured, and its lifetime needs tracked.


### 9. URLs should end in "/"
```C++
BMCWEB("/foo/bar");
```
Unless you explicitly have a reason not to (as there is one known exception
where the behavior must differ) all URL handlers should end in "/".  The bmcweb
route handler will detect routes ending in slash and generate routes for both
the route ending in slash and the one without.  This allows both URLs to be
used by users.  While many specifications do not require this, it resolves a
whole class of bug that we've seen in the past.


### 10. URLs constructed in aggregate
```C++
std::string routeStart = "/redfish/v1";

BMCWEB_ROUTE(routestart + "/SessionService/")
```
Very commonly, bmcweb maintainers and contributors alike have to do audits of
all routes that are available, to verify things like security and documentation
accuracy.  While these processes are largely manual, they can mostly be
conducted by a simple grep statement to search for urls in question.  Doing the
above makes the route handlers no longer greppable, and complicates bmcweb
patchsets as a whole.