Commit 2734e379 authored by Giuseppe Ottaviano's avatar Giuseppe Ottaviano Committed by Facebook Github Bot

Improve Format's handling of temporaries

Summary:
`FormatValue` holds non-integral objects by reference, which
can cause subtle problems if a formatter is constructed with temporary
arguments and used beyond the expression that constructs it.

With this diff the arguments are perfectly forwarded into a tuple, so
the formatter will take ownership of the temporaries while holding
references to lvalues as before.

The only downside is that now `FormatValue` objects are constructed
every time the formatter is used, rather than only at formatter
construction. This should not be noticeable as those objects'
constructors should just take a reference to the argument.

Note that the format string is still held by reference, but this is
fine because it should almost always be a string literal.

Reviewed By: ericniebler

Differential Revision: D5317382

fbshipit-source-id: ef8355194b634d3751ef1ccca32dd1db29e27c48
parent a198e715
...@@ -157,9 +157,7 @@ template <class Derived, bool containerMode, class... Args> ...@@ -157,9 +157,7 @@ template <class Derived, bool containerMode, class... Args>
BaseFormatter<Derived, containerMode, Args...>::BaseFormatter( BaseFormatter<Derived, containerMode, Args...>::BaseFormatter(
StringPiece str, StringPiece str,
Args&&... args) Args&&... args)
: str_(str), : str_(str), values_(std::forward<Args>(args)...) {}
values_(FormatValue<typename std::decay<Args>::type>(
std::forward<Args>(args))...) {}
template <class Derived, bool containerMode, class... Args> template <class Derived, bool containerMode, class... Args>
template <class Output> template <class Output>
...@@ -298,13 +296,9 @@ void formatString(StringPiece val, FormatArg& arg, FormatCallback& cb) { ...@@ -298,13 +296,9 @@ void formatString(StringPiece val, FormatArg& arg, FormatCallback& cb) {
throw BadFormatArg("folly::format: invalid precision"); throw BadFormatArg("folly::format: invalid precision");
} }
// XXX: clang should be smart enough to not need the two static_cast<size_t>
// uses below given the above checks. If clang ever becomes that smart, we
// should remove the otherwise unnecessary warts.
if (arg.precision != FormatArg::kDefaultPrecision && if (arg.precision != FormatArg::kDefaultPrecision &&
val.size() > static_cast<size_t>(arg.precision)) { val.size() > static_cast<size_t>(arg.precision)) {
val.reset(val.data(), size_t(arg.precision)); val.reset(val.data(), static_cast<size_t>(arg.precision));
} }
constexpr int padBufSize = 128; constexpr int padBufSize = 128;
...@@ -684,7 +678,7 @@ class FormatValue<float> { ...@@ -684,7 +678,7 @@ class FormatValue<float> {
float val_; float val_;
}; };
// Sring-y types (implicitly convertible to StringPiece, except char*) // String-y types (implicitly convertible to StringPiece, except char*)
template <class T> template <class T>
class FormatValue< class FormatValue<
T, T,
......
...@@ -51,9 +51,10 @@ class FormatterTag {}; ...@@ -51,9 +51,10 @@ class FormatterTag {};
/** /**
* Formatter class. * Formatter class.
* *
* Note that this class is tricky, as it keeps *references* to its arguments * Note that this class is tricky, as it keeps *references* to its lvalue
* (and doesn't copy the passed-in format string). Thankfully, you can't use * arguments (while it takes ownership of the temporaries), and it doesn't
* this directly, you have to use format(...) below. * copy the passed-in format string. Thankfully, you can't use this
* directly, you have to use format(...) below.
*/ */
/* BaseFormatter class. /* BaseFormatter class.
...@@ -103,14 +104,13 @@ class BaseFormatter { ...@@ -103,14 +104,13 @@ class BaseFormatter {
} }
/** /**
* metadata to identify generated children of BaseFormatter * Metadata to identify generated children of BaseFormatter
*/ */
typedef detail::FormatterTag IsFormatter; typedef detail::FormatterTag IsFormatter;
typedef BaseFormatter BaseType; typedef BaseFormatter BaseType;
private: private:
typedef std::tuple<FormatValue<typename std::decay<Args>::type>...> typedef std::tuple<Args...> ValueTuple;
ValueTuple;
static constexpr size_t valueCount = std::tuple_size<ValueTuple>::value; static constexpr size_t valueCount = std::tuple_size<ValueTuple>::value;
Derived const& asDerived() const { Derived const& asDerived() const {
...@@ -166,7 +166,7 @@ class BaseFormatter { ...@@ -166,7 +166,7 @@ class BaseFormatter {
K<valueCount, int>::type getSizeArgFrom(size_t i, const FormatArg& arg) K<valueCount, int>::type getSizeArgFrom(size_t i, const FormatArg& arg)
const { const {
if (i == K) { if (i == K) {
return getValue(std::get<K>(values_), arg); return getValue(getFormatValue<K>(), arg);
} }
return getSizeArgFrom<K + 1>(i, arg); return getSizeArgFrom<K + 1>(i, arg);
} }
...@@ -188,10 +188,19 @@ class BaseFormatter { ...@@ -188,10 +188,19 @@ class BaseFormatter {
// for the exclusive use of format() (below). This way, you can't create // for the exclusive use of format() (below). This way, you can't create
// a Formatter object, but can handle references to it (for streaming, // a Formatter object, but can handle references to it (for streaming,
// conversion to string, etc) -- which is good, as Formatter objects are // conversion to string, etc) -- which is good, as Formatter objects are
// dangerous (they hold references, possibly to temporaries) // dangerous (they may hold references).
BaseFormatter(BaseFormatter&&) = default; BaseFormatter(BaseFormatter&&) = default;
BaseFormatter& operator=(BaseFormatter&&) = default; BaseFormatter& operator=(BaseFormatter&&) = default;
template <size_t K>
using ArgType = typename std::tuple_element<K, ValueTuple>::type;
template <size_t K>
FormatValue<typename std::decay<ArgType<K>>::type> getFormatValue() const {
return FormatValue<typename std::decay<ArgType<K>>::type>(
std::get<K>(values_));
}
ValueTuple values_; ValueTuple values_;
}; };
...@@ -213,7 +222,7 @@ class Formatter : public BaseFormatter< ...@@ -213,7 +222,7 @@ class Formatter : public BaseFormatter<
template <size_t K, class Callback> template <size_t K, class Callback>
void doFormatArg(FormatArg& arg, Callback& cb) const { void doFormatArg(FormatArg& arg, Callback& cb) const {
std::get<K>(this->values_).format(arg, cb); this->template getFormatValue<K>().format(arg, cb);
} }
friend class BaseFormatter< friend class BaseFormatter<
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
#include <folly/Format.h> #include <folly/Format.h>
#include <folly/Utility.h>
#include <folly/portability/GTest.h> #include <folly/portability/GTest.h>
#include <string> #include <string>
...@@ -352,7 +352,7 @@ template <> class FormatValue<KeyValue> { ...@@ -352,7 +352,7 @@ template <> class FormatValue<KeyValue> {
const KeyValue& kv_; const KeyValue& kv_;
}; };
} // namespace } // namespace folly
TEST(Format, Custom) { TEST(Format, Custom) {
KeyValue kv { "hello", 42 }; KeyValue kv { "hello", 42 };
...@@ -477,7 +477,7 @@ class TestExtendingFormatter ...@@ -477,7 +477,7 @@ class TestExtendingFormatter
auto appender = [&result](StringPiece s) { auto appender = [&result](StringPiece s) {
result.append(s.data(), s.size()); result.append(s.data(), s.size());
}; };
std::get<K>(this->values_).format(arg, appender); this->template getFormatValue<K>().format(arg, appender);
result = sformat("{{{}}}", result); result = sformat("{{{}}}", result);
cb(StringPiece(result)); cb(StringPiece(result));
} }
...@@ -508,3 +508,55 @@ TEST(Format, Extending) { ...@@ -508,3 +508,55 @@ TEST(Format, Extending) {
"another formatter"), "another formatter"),
"Extending {a {formatter}} in {another formatter}"); "Extending {a {formatter}} in {another formatter}");
} }
TEST(Format, Temporary) {
constexpr StringPiece kStr = "A long string that should go on the heap";
auto fmt = format("{}", kStr.str()); // Pass a temporary std::string.
EXPECT_EQ(fmt.str(), kStr);
// The formatter can be reused.
EXPECT_EQ(fmt.str(), kStr);
}
namespace {
struct NoncopyableInt : MoveOnly {
explicit NoncopyableInt(int v) : value(v) {}
int value;
};
} // namespace
namespace folly {
template <>
class FormatValue<NoncopyableInt> {
public:
explicit FormatValue(const NoncopyableInt& v) : v_(v) {}
template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
FormatValue<int>(v_.value).format(arg, cb);
}
private:
const NoncopyableInt& v_;
};
} // namespace
TEST(Format, NoncopyableArg) {
{
// Test that lvalues are held by reference.
NoncopyableInt v(1);
auto fmt = format("{}", v);
EXPECT_EQ(fmt.str(), "1");
// The formatter can be reused.
EXPECT_EQ(fmt.str(), "1");
}
{
// Test that rvalues are moved.
auto fmt = format("{}", NoncopyableInt(1));
EXPECT_EQ(fmt.str(), "1");
}
}
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 to comment