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: 63 64- std::variant::get 65- std::vector::at 66- std::map::at 67- std::set::at 68- std::\<generic container type\>::at 69- nlohmann::json::operator!= 70- nlohmann::json::operator+= 71- nlohmann::json::at 72- nlohmann::json::get 73- nlohmann::json::get\_ref 74- nlohmann::json::get\_to 75- std::filesystem::create\_directory 76- std::filesystem::rename 77- std::filesystem::file\_size 78- std::stoi 79- std::stol 80- std::stoll 81 82#### special/strange case: 83 84nlohmann::json::parse by default throws on failure, but also accepts a optional 85argument that causes it to not throw. Please consult the other examples in the 86code for how to handle errors. 87 88 89#### Special note: Boost 90there is a whole class of boost asio functions that provide both a method that 91throws on failure, and a method that accepts and returns an error code. This is 92not a complete list, but users should verify in the boost docs when calling into 93asio methods, and prefer the one that returns an error code instead of throwing. 94 95- boost::asio::ip::tcp::acceptor::bind(); 96- boost::asio::ip::tcp::acceptor::cancel(); 97- boost::asio::ip::tcp::acceptor::close(); 98- boost::asio::ip::tcp::acceptor::listen(); 99- boost::asio::ip::address::make\_address(); 100 101### Blocking functions 102 103bmcweb uses a single reactor for all operations. Blocking that reactor for any 104amount of time causes all other operations to stop. The common blocking 105functions that tend to be called incorrectly are: 106 107- sleep() 108- boost::asio::ip::tcp::socket::read() 109- boost::asio::ip::tcp::socket::read\_some() 110- boost::asio::ip::tcp::socket::write() 111- boost::asio::ip::tcp::socket::write\_some() 112- boost::asio::ip::tcp::socket::connect() 113- boost::asio::ip::tcp::socket::send() 114- boost::asio::ip::tcp::socket::wait() 115- boost::asio::steady\_timer::wait() 116 117Note: an exception is made for filesystem/disk IO read and write. This is 118mostly due to not having great abstractions for it that mate well with the async 119system, the fact that most filesystem accesses are into tmpfs (and therefore 120should be "fast" most of the time) and in general how little the filesystem is 121used in practice. 122 123### Lack of locking between subsequent calls 124While global data structures are discouraged, they are sometimes required to 125store temporary state for operations that require it. Given the single 126threaded nature of bmcweb, they are not required to be explicitly threadsafe, 127but they must be always left in a valid state, and checked for other uses 128before occupying. 129 130```C++ 131std::optional<std::string> currentOperation; 132void firstCallbackInFlow(){ 133 currentOperation = "Foo"; 134} 135void secondCallbackInFlow(){ 136 currentOperation.reset(); 137} 138``` 139 140In the above case, the first callback needs a check to ensure that 141currentOperation is not already being used. 142 143### Wildcard reference captures in lambdas 144``` 145std::string x; auto mylambda = [&](){ 146 x = "foo"; 147} 148do_async_read(mylambda) 149``` 150 151Numerous times, lifetime issues of const references have been injected into 152async bmcweb code. While capturing by reference can be useful, given how 153difficult these types of bugs are to triage, bmcweb explicitly requires that all 154code captures variables by name explicitly, and calls out each variable being 155captured by value or by reference. The above prototypes would change to 156[&x]()... Which makes clear that x is captured, and its lifetime needs tracked. 157 158 159### URLs should end in "/" 160```C++ 161BMCWEB("/foo/bar"); 162``` 163Unless you explicitly have a reason not to (as there is one known exception 164where the behavior must differ) all URL handlers should end in "/". The bmcweb 165route handler will detect routes ending in slash and generate routes for both 166the route ending in slash and the one without. This allows both URLs to be 167used by users. While many specifications do not require this, it resolves a 168whole class of bug that we've seen in the past. 169 170 171### URLs constructed in aggregate 172```C++ 173std::string routeStart = "/redfish/v1"; 174 175BMCWEB_ROUTE(routestart + "/SessionService/") 176``` 177Very commonly, bmcweb maintainers and contributors alike have to do audits of 178all routes that are available, to verify things like security and documentation 179accuracy. While these processes are largely manual, they can mostly be 180conducted by a simple grep statement to search for urls in question. Doing the 181above makes the route handlers no longer greppable, and complicates bmcweb 182patchsets as a whole. 183