xref: /openbmc/bmcweb/COMMON_ERRORS.md (revision 57fce80e)
1# Commonly recurring errors in bmcweb
2
3What follows is a list of common errors that new users to bmcweb tend to make
4when operating within its bounds for the first time.  If this is your first time
5developing in bmcweb, the maintainers highly recommend reading and understanding
6_all_ of common traps before continuing with any development.  Every single one
7of the examples below compile without warnings, but are incorrect in
8not-always-obvious ways, or impose a pattern that tends to cause hard to find
9bugs, or bugs that appear later.  Every one has been submitted to code review
10multiple times.
11
12### Directly dereferencing a pointer without checking for validity first
13```C++
14int myBadMethod(const nlohmann::json& j){
15    const int* myPtr = j.get_if<int>();
16    return *myPtr;
17}
18```
19This pointer is not guaranteed to be filled, and could be a null dereference.
20
21### String views aren't null terminated
22```C++
23int getIntFromString(const std::string_view s){
24    return std::atoi(s.data());
25}
26```
27This will give the right answer much of the time, but has the possibility to
28fail when string\_view is not null terminated.  Use from\_chars instead, which
29takes both a pointer and a length
30
31### Not handling input errors
32```C++
33int getIntFromString(const std::string& s){
34    return std::atoi(s.c_str());
35}
36```
37In the case where the string is not representable as an int, this will trigger
38undefined behavior at system level.  Code needs to check for validity of the
39string, ideally with something like from\_chars, and return the appropriate error
40code.
41
42### Walking off the end of a string
43```C++
44std::string getFilenameFromPath(const std::string& path){
45    size_t index = path.find("/");
46    if (index != std::string::npos){
47        // If the string ends with "/", this will walk off the end of the string.
48        return path.substr(pos + 1);
49    }
50    return "";
51}
52```
53
54### Using methods that throw (or not handling bad inputs)
55```C++
56int myBadMethod(nlohmann::json& j){
57    return j.get<int>();
58}
59```
60This method throws, and bad inputs will not be handled
61
62Commonly used methods that fall into this pattern:
63std::variant::get
64std::vector::at
65std::map::at
66std::set::at
67std::<generic container type>::at
68nlohmann::json::operator!=
69nlohmann::json::operator+=
70nlohmann::json::at
71nlohmann::json::get
72nlohmann::json::get\_ref
73nlohmann::json::get\_to
74std::filesystem::create\_directory
75std::filesystem::rename
76std::filesystem::file\_size
77std::stoi
78std::stol
79std::stoll
80
81#### special/strange case:
82
83nlohmann::json::parse by default throws on failure, but also accepts a optional
84argument that causes it to not throw.  Please consult the other examples in the
85code for how to handle errors.
86
87
88#### Special note: Boost
89there is a whole class of boost asio functions that provide both a method that
90throws on failure, and a method that accepts and returns an error code.  This is
91not a complete list, but users should verify in the boost docs when calling into
92asio methods, and prefer the one that returns an error code instead of throwing.
93
94boost::asio::ip::tcp::acceptor::bind();
95boost::asio::ip::tcp::acceptor::cancel();
96boost::asio::ip::tcp::acceptor::close();
97boost::asio::ip::tcp::acceptor::listen();
98boost::asio::ip::address::make\_address();
99
100### Blocking functions
101
102bmcweb uses a single reactor for all operations.  Blocking that reactor for any
103amount of time causes all other operations to stop.  The common blocking
104functions that tend to be called incorrectly are:
105
106sleep()
107boost::asio::ip::tcp::socket::read()
108boost::asio::ip::tcp::socket::read\_some()
109boost::asio::ip::tcp::socket::write()
110boost::asio::ip::tcp::socket::write\_some()
111boost::asio::ip::tcp::socket::connect()
112boost::asio::ip::tcp::socket::send()
113boost::asio::ip::tcp::socket::wait()
114boost::asio::steady\_timer::wait()
115
116Note: an exception is made for filesystem/disk IO read and write.  This is
117mostly due to not having great abstractions for it that mate well with the async
118system, the fact that most filesystem accesses are into tmpfs (and therefore
119should be "fast" most of the time) and in general how little the filesystem is
120used in practice.
121
122### Lack of locking between subsequent calls
123While global data structures are discouraged, they are sometimes required to
124store temporary state for operations that require it.  Given the single
125threaded nature of bmcweb, they are not required to be explicitly threadsafe,
126but they must be always left in a valid state, and checked for other uses
127before occupying.
128
129```C++
130std::optional<std::string> currentOperation;
131void firstCallbackInFlow(){
132    currentOperation = "Foo";
133}
134void secondCallbackInFlow(){
135    currentOperation.reset();
136}
137```
138
139In the above case, the first callback needs a check to ensure that
140currentOperation is not already being used.
141
142### Wildcard reference captures in lambdas
143```
144std::string x; auto mylambda = [&](){
145    x = "foo";
146}
147do_async_read(mylambda)
148```
149
150Numerous times, lifetime issues of const references have been injected into
151async bmcweb code.  While capturing by reference can be useful, given how
152difficult these types of bugs are to triage, bmcweb explicitly requires that all
153code captures variables by name explicitly, and calls out each variable being
154captured by value or by reference.  The above prototypes would change to
155[&x]()... Which makes clear that x is captured, and its lifetime needs tracked.
156
157
158### URLs should end in "/"
159```C++
160BMCWEB("/foo/bar");
161```
162Unless you explicitly have a reason not to (as there is one known exception
163where the behavior must differ) all URL handlers should end in "/".  The bmcweb
164route handler will detect routes ending in slash and generate routes for both
165the route ending in slash and the one without.  This allows both URLs to be
166used by users.  While many specifications do not require this, it resolves a
167whole class of bug that we've seen in the past.
168
169
170### URLs constructed in aggregate
171```C++
172std::string routeStart = "/redfish/v1";
173
174BMCWEB_ROUTE(routestart + "/SessionService/")
175```
176Very commonly, bmcweb maintainers and contributors alike have to do audits of
177all routes that are available, to verify things like security and documentation
178accuracy.  While these processes are largely manual, they can mostly be
179conducted by a simple grep statement to search for urls in question.  Doing the
180above makes the route handlers no longer greppable, and complicates bmcweb
181patchsets as a whole.
182