Skip to content

Commit

Permalink
Error checking for wchar_t printf() incompatibility
Browse files Browse the repository at this point in the history
Attempting to format wchar_t* with "%ls" would result in conversion to
void*, and subsequent printing as a pointer - this could result in
unexpected results when converting from a previous usage of traditional
printf().  Changes in this patch make sure that such usage results in a
compile time error to minimize nasty surprises!

Also make the format string parser slightly more pedantic about which
format strings it will accept, by rejecting C99 hexadecimal %a and %A
with TINYFORMAT_ERROR.
  • Loading branch information
c42f committed Aug 29, 2012
1 parent d8e3d50 commit 3c7121f
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 12 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ CXXFLAGS?=-Wall

test: tinyformat_test_cxx98 tinyformat_test_cxx0x
@echo running tests...
@./tinyformat_test_cxx98 && ./tinyformat_test_cxx0x && echo "No errors"
@./tinyformat_test_cxx98 && \
./tinyformat_test_cxx0x && \
! $(CXX) $(CXXFLAGS) --std=c++98 -DTINYFORMAT_NO_VARIADIC_TEMPLATES \
-DTEST_WCHAR_T_COMPILE tinyformat_test.cpp 2> /dev/null && \
echo "No errors"

doc: tinyformat.html

Expand Down
23 changes: 14 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Design goals:
* Simplicity and minimalism. A single header file to include and distribute
with your own projects.
* Type safety and extensibility for user defined types.
* Parse standard C99 format strings
* Support as many commonly used C99 ``printf()`` features as practical without
compromising on simplicity.
* Use variadic templates with C++0x but provide good C++98 support for backward
Expand Down Expand Up @@ -180,8 +179,8 @@ Not all features of printf can be simulated simply using standard iostreams.
Here's a list of known incompatibilities:

* The C99 ``"%a"`` and ``"%A"`` hexadecimal floating point conversions are not
supported since the iostreams don't have the necessary flags. These add no
extra flags to the stream state but do trigger a conversion.
supported since the iostreams don't have the necessary flags. Using either
of these flags will result in a call to ``TINYFORMAT_ERROR``.
* The precision for integer conversions cannot be supported by the iostreams
state independently of the field width. (Note: **this is only a
problem for certain obscure integer conversions**; float conversions like
Expand All @@ -194,8 +193,14 @@ Here's a list of known incompatibilities:
simple solution within the iostream model.
* The ``"%n"`` query specifier isn't supported to keep things simple and will
result in a call to ``TINYFORMAT_ERROR``.
* Wide characters with the ``%ls`` conversion are not supported, though you
could write your own ``std::ostream`` insertion operator for ``wchar_t*``.
* The ``"%ls"`` conversion is not supported, and attempting to format a
``wchar_t`` array will cause a compile time error to minimise unexpected
surprises. If you know the encoding of your wchar_t strings, you could write
your own ``std::ostream`` insertion operator for them, and disable the
compile time check by defining the macro ``TINYFORMAT_ALLOW_WCHAR_STRINGS``.
If you want to print the *address* of a wide character with the ``"%p"``
conversion, you should cast it to a ``void*`` before passing it to one of the
formatting functions.


Error handling
Expand Down Expand Up @@ -343,7 +348,7 @@ Rationale

Or, why did I reinvent this particularly well studied wheel?

Nearly every program needs text formatting in some form but in most cases such
Nearly every program needs text formatting in some form but in many cases such
formatting is *incidental* to the main purpose of the program. In these cases,
you really want a library which is simple to use but as lightweight as
possible.
Expand Down Expand Up @@ -372,12 +377,12 @@ which need to do only a little formatting. Problems include
1. Having many large source files. This makes a heavy dependency unsuitable to
bundle within other projects for convenience.
2. Slow build times for every file using any sort of formatting (this is very
noticeable with boost/format.hpp. I'm not sure about the various other
alternatives.)
noticeable with g++ and boost/format.hpp. I'm not sure about the various
other alternatives.)
3. Code bloat due to instantiating many templates

Tinyformat tries to solve these problems while providing formatting which is
sufficiently general for incidental day to day uses.
sufficiently general and fast for incidental day to day uses.


License
Expand Down
17 changes: 16 additions & 1 deletion tinyformat.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ struct is_convertible
};


// Detect when a type is not a wchar_t string
template<typename T> struct is_wchar { typedef int tinyformat_wchar_is_not_supported; };
template<> struct is_wchar<wchar_t*> {};
template<> struct is_wchar<const wchar_t*> {};
template<int n> struct is_wchar<const wchar_t[n]> {};
template<int n> struct is_wchar<wchar_t[n]> {};


// Format the value by casting to type fmtT. This default implementation
// should never be called.
template<typename T, typename fmtT, bool convertible = is_convertible<T, fmtT>::value>
Expand Down Expand Up @@ -239,6 +247,11 @@ template<typename T>
inline void formatValue(std::ostream& out, const char* /*fmtBegin*/,
const char* fmtEnd, const T& value)
{
#ifndef TINYFORMAT_ALLOW_WCHAR_STRINGS
// Since we don't support printing of wchar_t using "%ls", make it fail at
// compile time in preference to printing as a void* at runtime.
typedef typename detail::is_wchar<T>::tinyformat_wchar_is_not_supported DummyType;
#endif
// The mess here is to support the %c and %p conversions: if these
// conversions are active we try to convert the type to a char or const
// void* respectively and format that instead of the value itself. For the
Expand Down Expand Up @@ -644,7 +657,9 @@ inline const char* FormatIterator::streamStateFromFormat(std::ostream& out,
out.flags(out.flags() & ~std::ios::floatfield);
break;
case 'a': case 'A':
break; // C99 hexadecimal floating point?? punt!
TINYFORMAT_ERROR("tinyformat: the %a and %A conversion specs "
"are not supported");
break;
case 'c':
// Handled as special case inside formatValue()
break;
Expand Down
19 changes: 18 additions & 1 deletion tinyformat_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ void compareSprintf(const Args&... args)

#define EXPECT_ERROR(expression) \
{ \
try { expression; assert(0 && "expected exception"); } \
try { expression; assert(0 && "expected exception in " \
#expression); } \
catch(std::runtime_error&) {} \
}

Expand Down Expand Up @@ -179,6 +180,22 @@ int unitTests()
tfm::format("%0.*d", "thing that can't convert to int", 42)
)

// Unhandled C99 format spec
EXPECT_ERROR(
tfm::format("%n", 10)
)
EXPECT_ERROR(
tfm::format("%a", 10)
)
EXPECT_ERROR(
tfm::format("%A", 10)
)

#ifdef TEST_WCHAR_T_COMPILE
// Test wchar_t handling - should fail to compile!
tfm::format("%ls", L"blah");
#endif

// Test that formatting is independent of underlying stream state.
std::ostringstream oss;
oss.width(20);
Expand Down

0 comments on commit 3c7121f

Please sign in to comment.