Commit 837d8e4e by FritzFlorian

Add test to prohibit usage of new.

We force linker errors if the new operator is used somewhere. See NOTES.md for details.
parent 829b6757
...@@ -28,6 +28,7 @@ add_subdirectory(lib/pls) ...@@ -28,6 +28,7 @@ add_subdirectory(lib/pls)
# Include examples # Include examples
add_subdirectory(app/playground) add_subdirectory(app/playground)
add_subdirectory(app/test_for_new)
# Add optional tests # Add optional tests
option(PACKAGE_TESTS "Build the tests" ON) option(PACKAGE_TESTS "Build the tests" ON)
......
# Notes
A collection of stuff that we noticed during development.
Useful later on two write a project report and to go back
in time to find out why certain decisions where made.
## 20.03.2018 - Prohibit New
We want to write this library without using any runtime memory
allocation to better fit the needs of the embedded marked.
To make sure we do not do so we add a trick:
we link an new implementation into the project (when testing) that
will cause an linker error if new is used somewhere.
If the linker reports such an error we can switch to debugging
by using a new implementation with a break point in it.
That way we for example ruled out std::thread, as we found the dynamic
memory allocation used in it.
## 20.03.2018 - callable objects and memory allocation / why we use no std::thread
When working with any sort of functionality that can be passed
to an object or function it is usually passed as:
1. an function pointer and a list of parameter values
2. an lambda, capturing any surrounding parameters needed
When we want to pass ANY functionality (with any number of parameters
or captured variables) we can not determine the amount of memory before
the call is done, making the callable (function + parameters) dynamicly
sized.
This can be a problem when implementing e.g. a thread class,
as the callable has to be stored somewhere. The **std::thread**
implementation allocates memory at runtime using **new** when
called with any form of parameters for the started function.
Because of this (and because the implementation can differ from
system to system) we decided to not provide an **std::thread** backend
for our internal thread class (that does not use dynamic memory,
as it lives on the stack, knowing its size at compile time using
templates).
Lambdas can be used, as long as we are sure the outer scope still exists
while executing (lambda lies in the callers stack), or if we copy the
lambda manually to some memory that we know will persist during the call.
It is important to know that the lambda wont be freed while it is
used, as the captured variables used inside the body are held in the
lambda object.
add_executable(test_for_new main.cpp)
# Example for adding the library to your app (as a cmake project dependency)
target_link_libraries(test_for_new pls)
#include <pls/internal/base/thread.h>
#include <pls/internal/base/prohibit_new.h>
using namespace pls::internal::base;
int global = 0;
int main() {
// Try to use every feature, to trigger the prohibited use of new if found somewhere
auto t1 = create_thread([] (){}, 0);
t1.start();
t1.join();
}
...@@ -4,7 +4,7 @@ add_library(pls STATIC ...@@ -4,7 +4,7 @@ add_library(pls STATIC
include/pls/internal/base/choose_threading.h include/pls/internal/base/choose_threading.h
src/internal/base/spin_lock.cpp include/pls/internal/base/spin_lock.h src/internal/base/spin_lock.cpp include/pls/internal/base/spin_lock.h
src/internal/base/thread.cpp include/pls/internal/base/thread.h src/internal/base/thread.cpp include/pls/internal/base/thread.h
) include/pls/internal/base/prohibit_new.h)
# Settings for our project... # Settings for our project...
# ...pthreads or C++ 11 threads # ...pthreads or C++ 11 threads
......
// Prevents the use of the new operator by forcing a linker error if it is used.
// Simply include this in your application and the linker will tell you if you
// use new somewhere (even in third party code linked to your application).
#ifndef PLS_PROHIBIT_NEW_H
#define PLS_PROHIBIT_NEW_H
#include <iostream>
// Comment this out and set a debug point in the std::cout
// line to find out where the new is located in case you
// get an linker error.
//#define NEW_LINK_ERROR
#ifdef NEW_LINK_ERROR
// This will cause a linker error if new is used in the code.
// We also exit if it is somehow still called.
inline void * operator new (std::size_t) {
extern int bare_new_erroneously_called();
exit(bare_new_erroneously_called() | 1);
}
#else
// Use this + debugging point to find out where we use a new
inline void * operator new (std::size_t) {
// extern int bare_new_erroneously_called();
std::cout << "New called! Exiting application, no dynamic memory allocation allowed!";
exit(1);
}
#endif
#endif //PLS_PROHIBIT_NEW_H
...@@ -50,7 +50,7 @@ namespace pls { ...@@ -50,7 +50,7 @@ namespace pls {
return reinterpret_cast<T*>(pthread_getspecific(local_storage_key_)); return reinterpret_cast<T*>(pthread_getspecific(local_storage_key_));
#endif #endif
#ifdef PLS_USING_CPP_THREADS #ifdef PLS_USING_CPP_THREADS
reinterpret_cast<T*>(local_storage_); return reinterpret_cast<T*>(local_storage_);
#endif #endif
} }
...@@ -111,13 +111,16 @@ namespace pls { ...@@ -111,13 +111,16 @@ namespace pls {
explicit thread(const Function& function, const State& state): explicit thread(const Function& function, const State& state):
function_{function}, function_{function},
state_{state}, state_{state},
std_thread_{0} {}; std_thread_{} {};
static void start_internal(void* my_thread_pointer) {
auto my_thread = reinterpret_cast<thread*>(my_thread_pointer);
this_thread::local_storage_ = reinterpret_cast<void*>(&my_thread->state_);
my_thread->function_();
}
void start() { void start() {
std_thread_ = std::thread([=](){ std_thread_ = std::thread(start_internal, (void*)this);
local_storage = reinterpret_cast<void*><&this->state_>;
this->function_();
});
} }
#endif #endif
public: public:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment