New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Printf like checks #62

Closed
newnon opened this Issue Aug 29, 2014 · 11 comments

Comments

Projects
None yet
2 participants
@newnon
Contributor

newnon commented Aug 29, 2014

some compilers (gcc,clang) can check printf like functions
in Os X it works like this

int printf(const char * __restrict, ...) __printflike(1, 2);

int sprintf(char * __restrict, const char * __restrict, ...) __printflike(2, 3);
int sscanf(const char * __restrict, const char * __restrict, ...) __scanflike(2, 3);
int vsprintf(char * __restrict, const char * __restrict, va_list) __printflike(2, 0);

int snprintf(char * __restrict, size_t, const char * __restrict, ...) __printflike(3, 4);
int vsnprintf(char * __restrict, size_t, const char * __restrict, va_list) __printflike(3, 0);

define __printflike(fmtarg, firstvararg) \

    __attribute__((__format__ (__printf__, fmtarg, firstvararg)))

i fink it good idea to add something similar to your library

you can take some additional information here https://mail-index.netbsd.org/tech-userlevel/2012/03/04/msg006197.html

@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Aug 29, 2014

Contributor

Unfortunately the format attribute only works with varargs as can be seen from the following example:

template <typename T>
void print(const char *, const T &) __attribute__ ((format (printf, 1, 2)));

template <typename T>
void print(const char *, const T &) {}

int main() {
  print("%s", 42);
}
$ g++ test.cc
test.cc: In substitution of ‘template<class T> void print(const char*, const T&) [with T = int]’:
test.cc:8:17:   required from here
test.cc:5:6: error: args to be formatted is not ‘...’
 void print(const char *, const T &) {}
      ^

This library uses variadic templates (emulated on pre-C++11) instead of varargs because varargs are inherently unsafe. While it doesn't provide compile-time warnings for literal format strings, it catches all type errors and reports them as exception. Unlike the format attribute this works with non-literal format strings too.

Contributor

vitaut commented Aug 29, 2014

Unfortunately the format attribute only works with varargs as can be seen from the following example:

template <typename T>
void print(const char *, const T &) __attribute__ ((format (printf, 1, 2)));

template <typename T>
void print(const char *, const T &) {}

int main() {
  print("%s", 42);
}
$ g++ test.cc
test.cc: In substitution of ‘template<class T> void print(const char*, const T&) [with T = int]’:
test.cc:8:17:   required from here
test.cc:5:6: error: args to be formatted is not ‘...’
 void print(const char *, const T &) {}
      ^

This library uses variadic templates (emulated on pre-C++11) instead of varargs because varargs are inherently unsafe. While it doesn't provide compile-time warnings for literal format strings, it catches all type errors and reports them as exception. Unlike the format attribute this works with non-literal format strings too.

@vitaut vitaut closed this Aug 29, 2014

@newnon

This comment has been minimized.

Show comment
Hide comment
@newnon

newnon Aug 29, 2014

Contributor

thanks for fast reply, but sometime it not good practice to use exceptions. I will think about possibility to deal with new c++11&(14) functions may be some idea from this post http://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/
if your string constexpr not variable it's theoretical possible to check it in compile time

Contributor

newnon commented Aug 29, 2014

thanks for fast reply, but sometime it not good practice to use exceptions. I will think about possibility to deal with new c++11&(14) functions may be some idea from this post http://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/
if your string constexpr not variable it's theoretical possible to check it in compile time

@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Aug 29, 2014

Contributor

Thanks for the suggestion and the link. I think this can be used to catch at least some errors in literal format strings. Whether this is really practical needs further investigation though. Reopening the issue.

Contributor

vitaut commented Aug 29, 2014

Thanks for the suggestion and the link. I think this can be used to catch at least some errors in literal format strings. Whether this is really practical needs further investigation though. Reopening the issue.

@vitaut vitaut reopened this Aug 29, 2014

@vitaut vitaut added the enhancement label Aug 29, 2014

@newnon

This comment has been minimized.

Show comment
Hide comment
@newnon

newnon Aug 29, 2014

Contributor

i've just found solution on some Russian resource
i create gist for demonstrating some solution and it works https://gist.github.com/newnon/34de828099825749e5e9
i just compile it on my mac look like it can found simple mistakes
full post in english http://sysmagazine.com/posts/142352/

Contributor

newnon commented Aug 29, 2014

i've just found solution on some Russian resource
i create gist for demonstrating some solution and it works https://gist.github.com/newnon/34de828099825749e5e9
i just compile it on my mac look like it can found simple mistakes
full post in english http://sysmagazine.com/posts/142352/

@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Aug 29, 2014

Contributor

Fascinating. To make this practical, however, the whole printf format string parsing logic (https://github.com/cppformat/cppformat/blob/master/format.cc#L865) will have to be replicated in constexpr form. Also it is not clear whether this approach can be extended to support positional arguments (I guess it can, but I haven't tried it).

As the translation is not very good, here's a link to the same blog post in Russian for future reference: http://habrahabr.ru/post/142352/

Contributor

vitaut commented Aug 29, 2014

Fascinating. To make this practical, however, the whole printf format string parsing logic (https://github.com/cppformat/cppformat/blob/master/format.cc#L865) will have to be replicated in constexpr form. Also it is not clear whether this approach can be extended to support positional arguments (I guess it can, but I haven't tried it).

As the translation is not very good, here's a link to the same blog post in Russian for future reference: http://habrahabr.ru/post/142352/

@newnon

This comment has been minimized.

Show comment
Hide comment
@newnon

newnon Sep 1, 2014

Contributor

without changing a syntax it's not possible the only one way to do this something like
fmt::print("Hello, {}!").format("world");
because inside function we loose constexp type
i found this repository with partial working lib https://github.com/mpark/format
and some more ideas here http://stackoverflow.com/questions/15858141/conveniently-declaring-compile-time-strings-in-c
and once more compile time library to work with different types https://github.com/bolero-MURAKAMI/Sprout

Contributor

newnon commented Sep 1, 2014

without changing a syntax it's not possible the only one way to do this something like
fmt::print("Hello, {}!").format("world");
because inside function we loose constexp type
i found this repository with partial working lib https://github.com/mpark/format
and some more ideas here http://stackoverflow.com/questions/15858141/conveniently-declaring-compile-time-strings-in-c
and once more compile time library to work with different types https://github.com/bolero-MURAKAMI/Sprout

@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Sep 25, 2014

Contributor

With the recent improvements to the library (passing argument type information separately from the arguments themselves), I think it might be possible to safely use varargs and the format attribute for fmt::printf. I'll investigate this further in #72.

Contributor

vitaut commented Sep 25, 2014

With the recent improvements to the library (passing argument type information separately from the arguments themselves), I think it might be possible to safely use varargs and the format attribute for fmt::printf. I'll investigate this further in #72.

@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Mar 6, 2015

Contributor

It occurred to me that you can have compile-time checking of printf-style format string by using a helper function and a macro as illustrated with the following example:

#include "format.h"

void check_args(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

#define FMT_PRINTF(...) \
  if (false) check_args(__VA_ARGS__); \
  fmt::printf(__VA_ARGS__);

int main() {
  try {
    FMT_PRINTF("%s", 42);
  } catch (const std::exception &e) {
    fmt::print("error: {}\n", e.what());
  }
}

Compiling it with GCC gives the desired warning:

$ g++ test.cc format.cc
test.cc: In function ‘int main()’:
test.cc:6:36: warning: format ‘%s’ expects argument of type ‘char*’, but argument 2 has type ‘int’ [-Wformat=]
   if (false) check_args(__VA_ARGS__); \
                                    ^
test.cc:11:5: note: in expansion of macro ‘FMT_PRINTF’
     FMT_PRINTF("%s", 42);
     ^

check_args is a helper function that is never called and its arguments are not evaluated so there is no runtime overhead.

And if you ignore the warning, it's still safe. You get an exception at runtime:

$ ./a.out 
error: unknown format code 's' for integer
Contributor

vitaut commented Mar 6, 2015

It occurred to me that you can have compile-time checking of printf-style format string by using a helper function and a macro as illustrated with the following example:

#include "format.h"

void check_args(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

#define FMT_PRINTF(...) \
  if (false) check_args(__VA_ARGS__); \
  fmt::printf(__VA_ARGS__);

int main() {
  try {
    FMT_PRINTF("%s", 42);
  } catch (const std::exception &e) {
    fmt::print("error: {}\n", e.what());
  }
}

Compiling it with GCC gives the desired warning:

$ g++ test.cc format.cc
test.cc: In function ‘int main()’:
test.cc:6:36: warning: format ‘%s’ expects argument of type ‘char*’, but argument 2 has type ‘int’ [-Wformat=]
   if (false) check_args(__VA_ARGS__); \
                                    ^
test.cc:11:5: note: in expansion of macro ‘FMT_PRINTF’
     FMT_PRINTF("%s", 42);
     ^

check_args is a helper function that is never called and its arguments are not evaluated so there is no runtime overhead.

And if you ignore the warning, it's still safe. You get an exception at runtime:

$ ./a.out 
error: unknown format code 's' for integer
@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Jun 4, 2015

Contributor

Looking at various options:

  1. https://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/ : constexpr string checker - doesn't help here because it only works with constexpr functions while formatting arguments are not constant in general.

  2. http://habrahabr.ru/post/142352/ & http://sysmagazine.com/posts/142352/ : macro around formatting string to convert it into template arguments checked with constexpr:

    #define TEMPLATE_LITERAL(LITERAL) TEMPLATE_LITERAL_BASE(MAX_SYM, LITERAL)
    safe_printf_2(TEMPLATE_LITERAL("%c %d\n"), 'v', 2);

    Drawbacks: relies on macros and requires specification of max string size.

  3. https://github.com/mpark/format : similar to case 2 but with a different syntax:

    #define FS(format) MakeFormatStr(MAKE_CHARS(format))
    ToString(FS("so much depends upon {0}").Format("a red wheel barrow"))

    This also relies on macros.

So (1) will not work here while (2) and (3) don't look particularly appealing. Therefore I'm going to close this issue until a better option is found.

Contributor

vitaut commented Jun 4, 2015

Looking at various options:

  1. https://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/ : constexpr string checker - doesn't help here because it only works with constexpr functions while formatting arguments are not constant in general.

  2. http://habrahabr.ru/post/142352/ & http://sysmagazine.com/posts/142352/ : macro around formatting string to convert it into template arguments checked with constexpr:

    #define TEMPLATE_LITERAL(LITERAL) TEMPLATE_LITERAL_BASE(MAX_SYM, LITERAL)
    safe_printf_2(TEMPLATE_LITERAL("%c %d\n"), 'v', 2);

    Drawbacks: relies on macros and requires specification of max string size.

  3. https://github.com/mpark/format : similar to case 2 but with a different syntax:

    #define FS(format) MakeFormatStr(MAKE_CHARS(format))
    ToString(FS("so much depends upon {0}").Format("a red wheel barrow"))

    This also relies on macros.

So (1) will not work here while (2) and (3) don't look particularly appealing. Therefore I'm going to close this issue until a better option is found.

@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Aug 18, 2016

Contributor

As discussed on Reddit it might be possible to use UDL for compile-time checks:

[–]redditsoaddicting 4 points 11 hours ago
The key is to have standard support for making it possible to have a UDL for compile-time strings. The author of Boost Hana is working on getting that standardized. Then you add a suffix to the format string and voila. I'll come up with an example later.
permalinkembedsaveparentreportgive goldreply
[–]redditsoaddicting 2 points 10 hours ago
/u/aearphen Here's an example. It has a ways to go, but it's certainly a start. Feel free to try out each of the commented lines.
The main problem in the example is my lack of ability to turn the check "each { has a } immediately following it" into a functional solution. I'd be happy to know a good approach for that.
Pay special attention to how we can choose to put in compile-time checking if we know it's a compile-time string. I didn't show any actual work being done to format, but given a uniform interface over compile-time and runtime strings (which should be manageable), you end up writing one function with one implementation for both compile-time and runtime strings. Awesome stuff!

Example:

#include <iostream>

#define BOOST_HANA_CONFIG_ENABLE_STRING_UDL
#include <boost/hana.hpp>

namespace hana = boost::hana;
using namespace hana::literals;

template<typename String, typename... Ts>
void foo(String fmt, Ts...) {
    if constexpr (hana::is_a<hana::string_tag>(fmt)) {
        static_assert(hana::back(fmt) != '{', "Why would you end your format string with '{'?");
        if constexpr (hana::back(fmt) != '{') { return; }

        (hana::size(fmt) - hana::size_c<1>).times.with_index([&](auto i) {
            if constexpr (fmt[i] == '{') {
                static_assert(fmt[i + hana::size_c<1>] == '}', "This is an example and only supports empty '{}', dummy.");
            }
        });
    } else {
        std::cout << "We don't have a compile-time string. We can still use it: " << fmt << "\n";   
    }
}

int main() {
    //foo("abc"_s);
    //foo("abc {} def"_s);
    foo("abc def {"_s);
    //foo("abc {foo} def"_s);

    //foo("abc def");
}
Contributor

vitaut commented Aug 18, 2016

As discussed on Reddit it might be possible to use UDL for compile-time checks:

[–]redditsoaddicting 4 points 11 hours ago
The key is to have standard support for making it possible to have a UDL for compile-time strings. The author of Boost Hana is working on getting that standardized. Then you add a suffix to the format string and voila. I'll come up with an example later.
permalinkembedsaveparentreportgive goldreply
[–]redditsoaddicting 2 points 10 hours ago
/u/aearphen Here's an example. It has a ways to go, but it's certainly a start. Feel free to try out each of the commented lines.
The main problem in the example is my lack of ability to turn the check "each { has a } immediately following it" into a functional solution. I'd be happy to know a good approach for that.
Pay special attention to how we can choose to put in compile-time checking if we know it's a compile-time string. I didn't show any actual work being done to format, but given a uniform interface over compile-time and runtime strings (which should be manageable), you end up writing one function with one implementation for both compile-time and runtime strings. Awesome stuff!

Example:

#include <iostream>

#define BOOST_HANA_CONFIG_ENABLE_STRING_UDL
#include <boost/hana.hpp>

namespace hana = boost::hana;
using namespace hana::literals;

template<typename String, typename... Ts>
void foo(String fmt, Ts...) {
    if constexpr (hana::is_a<hana::string_tag>(fmt)) {
        static_assert(hana::back(fmt) != '{', "Why would you end your format string with '{'?");
        if constexpr (hana::back(fmt) != '{') { return; }

        (hana::size(fmt) - hana::size_c<1>).times.with_index([&](auto i) {
            if constexpr (fmt[i] == '{') {
                static_assert(fmt[i + hana::size_c<1>] == '}', "This is an example and only supports empty '{}', dummy.");
            }
        });
    } else {
        std::cout << "We don't have a compile-time string. We can still use it: " << fmt << "\n";   
    }
}

int main() {
    //foo("abc"_s);
    //foo("abc {} def"_s);
    foo("abc def {"_s);
    //foo("abc {foo} def"_s);

    //foo("abc def");
}
@vitaut

This comment has been minimized.

Show comment
Hide comment
@vitaut

vitaut Nov 6, 2017

Contributor

Compile-time format string checks are now available: http://zverovich.net/2017/11/05/compile-time-format-strings.html

Contributor

vitaut commented Nov 6, 2017

Compile-time format string checks are now available: http://zverovich.net/2017/11/05/compile-time-format-strings.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment