Secretchronicles / TSC

An open source two-dimensional platform game.
https://secretchronicles.org/
GNU General Public License v3.0
205 stars 49 forks source link

Replace sprintf calls with stringstream #119

Open datahead8888 opened 10 years ago

datahead8888 commented 10 years ago

As discovered in #7 there are some calls to sprintf in the code.

Example - hud.cpp:

sprintf( text, _("Points %08d"), static_cast<int>(pLevel_Player->m_points) );

In order to be consistent with our decision to use cout/cerr rather than printf, we should replace these with the C++ equivalent -- stringstream.

C++ reference for stringstream: http://www.cplusplus.com/reference/sstream/stringstream/stringstream/

Quintus commented 9 years ago

Is this still relevant?

Vale, Quintus

datahead8888 commented 9 years ago

Is this still relevant?

These calls are still in the code, yes. If we want to use a C++ standard, this task cannot be closed.

Luiji commented 9 years ago

@datahead8888 Using sprintf is fully within the bounds of the C++ standard. However, consistently with our use of C++ templates would be correct.

Additionally, sprintf is insecure because it has no buffer overflow checks which is not a problem with stringstream which dynamically resizes its internal buffer.

EDIT: If Quintus agrees I'll go ahead, grep through the code and replace all instances of sprintf.

datahead8888 commented 9 years ago

We had made a decision to replace printf with cout / cerr where possible, with the viewpoint that it is more C++ like.

I didn't mean that sprint / printf cannot be used in C++ legitimately. I just meant stringstream would be more consistent with our interpretation of leveraging C++.

This task isn't urgent - I just filed it so that we would have a record of it.

Quintus commented 9 years ago

sprintf() may have its security flaws, but there are some formatting cases it handles really good you have a hard time coding by hand with stringstream or whatever. However, we don’t have such in the codebase of TSC I guess, so replacing it with stringstream or other means is apropriate. If you find yourself you have to create 50+ lines just to replace a sprintf() call, then you want to keep sprintf().

Valete, Quintus

Luiji commented 9 years ago

@Quintus In such instances where sprintf formatting is necessary it is much more secure to use snprintf which maintains buffer overflow checks. I'd be fine with replacing it that way as well, since normally stringstream-based checks take up more code. Additionally, I realize now that even the example @datahead8888 provides would be much simpler using a printf formatters.

I would like to propose that we change our decision to instead replace all sprintf calls with snprintf calls.

datahead8888 commented 9 years ago

I'd be fine with replacing it that way as well, since normally stringstream -based checks take up more code.

cout usually takes a little more code than printf as well (though it's not quite as bad as stringstream is). My thought is that we might consider going ahead and using stringstream to be consistent unless it really bloats the code.

snprintf would be better than sprintf, however.

Luiji commented 9 years ago

@datahead8888 Consistency shouldn't mean bloating the code with unnecessary constructs. The less code there is the more maintainable it will be overall.

Quintus commented 9 years ago

I would like to propose that we change our decision to instead replace all sprintf calls with snprintf calls.

Is snprintf() properly standardised so that we can rely on it even being available in Microsoft’s Virtually broken C compiler (MSVC)?

If so, I don’t see a problem in using it.

Vale, Quintus

datahead8888 commented 9 years ago

I'm fine with what you think is best on this.

Luiji commented 9 years ago

@Quintus I...I was about to say "yes, of course!" but apparently I've overestimated Microsoft. Instead, we're stuck with their printf_s and _snprintf (note: _snprintf != snprintf because screw you). Apparently snprintf is C++0x standard.

It may be worth just sticking with sprintf because of this, unless you want to add a dependency on Boost.Format or a custom implementation of sprintf, or are willing to go off standard and depend on snprintf when available and use a stub function on MSVC like this:

#ifdef _MSC_VER

#define snprintf c99_snprintf

inline int c99_snprintf(char* str, size_t size, const char* format, ...)
{
    int count;
    va_list ap;

    va_start(ap, format);
    count = c99_vsnprintf(str, size, format, ap);
    va_end(ap);

    return count;
}

inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
{
    int count = -1;

    if (size != 0)
        count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
    if (count == -1)
        count = _vscprintf(format, ap);

    return count;
}

#endif // _MSC_VER

(Courtesy of https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010)

In the end, it may be worth just putting this on the back burner until C++0x finds it's way into MSVC or we deem MSVC non-priority.

Quintus commented 9 years ago

Also, one thing to consider with regard to internationalisation. Gettext highly recommends the use of format strings (see the manual) so that translators can move the formatting directives around to better fit their language. For any content passed to Gettext via the _() macro family, using sprintf() or one of the similar functions is required.

unless you want to add a dependency on Boost.Format

No, definitely I do not want yet another dependency.

use a stub function on MSVC like this:

This seems to be the best solution, but I wouldn’t test for the MSVC macro like this. Once MSVC supports snprintf(), it would clash. It is more useful to do it like this:

#if defined(_MSC_VER) && _MSC_VER <= 0xwhateverversioniscurrent
//...
#endif

Vale, Quintus

datahead8888 commented 9 years ago

Honestly, if we have to write logic to compile differently for Visual C++ in order to implement snprintf, I think we should just close this ticket as "Won't Implement". That is even more complicated than stringstream, which at least is used consistently by different compilers and accepted by the community.

Yes, I think we should eventually support Visual C++ on some level with documentation on how to get TSC to work in it. When I took a real time rendering class last semester, Visual C++ was the most popular compiler. Having it available for developers to use will probably increase the number of programmers who contribute. At the very least our code has to compile in it; the moment we drop compilation support for Visual C++ it may become impractical to add it back. I use Visual C++ regularly myself and have several features I like in it that as far as I know either are not supported by g++ or would probably require additional, separate tools to get.

Luiji commented 9 years ago

@Quintus:

Once MSVC supports snprintf(), it would clash.

Not necessarily. That code was specifically designed not to clash. It defines the real functions under a different name and then defines the macro such that it will only affect subsequent usage. It would only cause problems of snprintf is used in inline functions in headers, assuming that you included stdio.h before this definition occurs.

However, if you want to avoid that possibility as well, you can instead put in this macro:

#ifndef _MSC_VER
#define tsc_snprintf snprintf
#else
inline int tsc_snprintf(char* str, size_t size, const char* format, ...)
{
    int count;
    va_list ap;

    va_start(ap, format);
    count = tsc_vsnprintf(str, size, format, ap);
    va_end(ap);

    return count;
}

inline int tsc_vsnprintf(char* str, size_t size, const char* format, va_list ap)
{
    int count = -1;

    if (size != 0)
        count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
    if (count == -1)
        count = _vscprintf(format, ap);

    return count;
}
#endif

This may be preferable than checking _MSC_VER <= 0x... since we won't have to update it for every release that _MSC_VER increases without snprintf support.

@datahead8888: My main problem with not fixing this yet is that usage of sprintf may result in random behavior as other parts of memory are overwritten or segmentation faults. These sort of bugs are some of the most difficult to trace since the former does not raise exceptions and the later results in an immediate abort.

Quintus commented 9 years ago

@Luiji Looks fine to me. Pin a comment on why we need this construct above it, and commit it to the repository as a separate C++ header file, probably core/compiler-specifics.hpp or something like that, so that all the compiler-specific stuff we may also add to in the future is contained in one single file.

Once done, replacing the sprintf() calls can start.

Vale, Quintus