novelrt / NovelRT

A cross-platform 2D game engine accompanied by a strong toolset for visual novels.
MIT License
185 stars 42 forks source link

#388 memcpy_s removal #572

Closed RyadaProductions closed 1 year ago

RyadaProductions commented 1 year ago

Please check if the PR fulfills these requirements

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...) Changes the memcpy_s instances to use memcpy directly but adds asserts for debug time issues and if applicable nullptr checks on the destination/source

Is there an open issue that this resolves? If so, please link it here.

What is the current behavior? (You can also link to an open issue here) N/A

What is the new behavior (if this is a feature change)? N/A

Does this PR introduce a breaking change? (What changes might users need to make in their application due to this PR?) No breaking change for the end user

Other information: N/A

tannergooding commented 1 year ago

Changes the memcpy_s instances to use memcpy directly but adds asserts for debug time issues and if applicable nullptr checks on the destination/source

Why? What's the reasoning for not using an official C/C++ API in favor of rolling our own equivalent?

RubyNova commented 1 year ago

Why? What's the reasoning for not using an official C/C++ API in favor of rolling our own equivalent?

Because we can't actually use it in all locations and the errno_t stuff is additional faff I can't be arsed with, to put it flatly. It's easier for us to just validate the memcpy call is correct and be done. Only windows supports memcpy_s and I'd rather not maintain two branches of the code in this case.

RyadaProductions commented 1 year ago

On the case of reusing a selfmade method for the assertions: Technically I could add a method that we can reuse like memcpy_validation but since we don't use memcpy that often I figured asserting it at the start of a method where we expect certain things to be passed in is cleaner. Ofc if we want a single method that does the assertion as well as the memcpy I'm not opposed to that but in this case I feel this is safer.

tannergooding commented 1 year ago

Ofc if we want a single method that does the assertion as well as the memcpy I'm not opposed to that but in this case I feel this is safer.

Rolling your own validation logic, especially logic that has to be duplicated at each callsite, is almost never safer ;)

Centralizing the checks to one place such that things can be correctly accounted for and will always be accounted for is the safest thing you can do.

In this case, if for some reason memcpy_s isn't available (such as GCC/Clang decided to not implement Annex K) then it'd be better to simply define a memcpy_s shim with the right surface area and to place the validation logic in that.

If for some reason you don't want to handle errno_t at each call site, then likewise define some C++ MemoryCopy utility that wraps memcpy_s, checks errno_t, and throws a proper exception on failure.

RubyNova commented 1 year ago

Yeah they explicitly did not implement it in Linux/macOS/console land because "its a stupid API" from what I remember of reading up on it previously.

Very helpful, I know. I'm not in pain at all. :)

tannergooding commented 1 year ago

So why not define our own MemoryCopy that wraps the right APIs where available and does the checks otherwise.

This centralizes everything, allows easy modification in the future if we need (say we want to disable the checks in release builds), allows ensuring consistent behavior (such as throwing C++ exceptions for failures or asserting correct results), etc.

RubyNova commented 1 year ago

That is a possible solution. The original GH issue was just the minimally required work and I kind of wrote it out of frustration mid-Vulkan pipeline fixing ages ago. That's probably why. :D

tannergooding commented 1 year ago

Ah, I missed that there was an issue at all.

Would be helpful to have it show as a regular issue https://github.com/novelrt/NovelRT/issues/388 and not as link it here

I think it would be much better/safer/extensible to make our own wrapper method in such cases. Having manually placed validation at each memcpy callsite is overall error prone and easy to forget to do. Wrapper ensures it always happens, can do the relevant kind of checks, expose the right kind of errors, and can easily change behavior for all callsites based on perf, security, or other needs.

FiniteReality commented 1 year ago

Frankly, we should update memcpy calls to std::copy over a std::span/gsl::span. In addition, the APIs that return raw pointers should be updated to also use a span type - the C API would translate this to something along the lines of void* fn(size_t* size) and write the span's size to the size parameter if it's non-null, as is typical for C APIs.