// Copyright 2020 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "absl/strings/internal/str_format/bind.h" #include <cerrno> #include <limits> #include <sstream> #include <string> namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { namespace { inline bool BindFromPosition(int position, int* value, absl::Span<const FormatArgImpl> pack) { assert(position > 0); if (static_cast<size_t>(position) > pack.size()) { return false; } // -1 because positions are 1-based return FormatArgImplFriend::ToInt(pack[position - 1], value); } class ArgContext { public: explicit ArgContext(absl::Span<const FormatArgImpl> pack) : pack_(pack) {} // Fill 'bound' with the results of applying the context's argument pack // to the specified 'unbound'. We synthesize a BoundConversion by // lining up a UnboundConversion with a user argument. We also // resolve any '*' specifiers for width and precision, so after // this call, 'bound' has all the information it needs to be formatted. // Returns false on failure. bool Bind(const UnboundConversion* unbound, BoundConversion* bound); private: absl::Span<const FormatArgImpl> pack_; }; inline bool ArgContext::Bind(const UnboundConversion* unbound, BoundConversion* bound) { const FormatArgImpl* arg = nullptr; int arg_position = unbound->arg_position; if (static_cast<size_t>(arg_position - 1) >= pack_.size()) return false; arg = &pack_[arg_position - 1]; // 1-based if (unbound->flags != Flags::kBasic) { int width = unbound->width.value(); bool force_left = false; if (unbound->width.is_from_arg()) { if (!BindFromPosition(unbound->width.get_from_arg(), &width, pack_)) return false; if (width < 0) { // "A negative field width is taken as a '-' flag followed by a // positive field width." force_left = true; // Make sure we don't overflow the width when negating it. width = -std::max(width, -std::numeric_limits<int>::max()); } } int precision = unbound->precision.value(); if (unbound->precision.is_from_arg()) { if (!BindFromPosition(unbound->precision.get_from_arg(), &precision, pack_)) return false; } FormatConversionSpecImplFriend::SetWidth(width, bound); FormatConversionSpecImplFriend::SetPrecision(precision, bound); if (force_left) { FormatConversionSpecImplFriend::SetFlags(unbound->flags | Flags::kLeft, bound); } else { FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound); } } else { FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound); FormatConversionSpecImplFriend::SetWidth(-1, bound); FormatConversionSpecImplFriend::SetPrecision(-1, bound); } FormatConversionSpecImplFriend::SetConversionChar(unbound->conv, bound); bound->set_arg(arg); return true; } template <typename Converter> class ConverterConsumer { public: ConverterConsumer(Converter converter, absl::Span<const FormatArgImpl> pack) : converter_(converter), arg_context_(pack) {} bool Append(string_view s) { converter_.Append(s); return true; } bool ConvertOne(const UnboundConversion& conv, string_view conv_string) { BoundConversion bound; if (!arg_context_.Bind(&conv, &bound)) return false; return converter_.ConvertOne(bound, conv_string); } private: Converter converter_; ArgContext arg_context_; }; template <typename Converter> bool ConvertAll(const UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args, Converter converter) { if (format.has_parsed_conversion()) { return format.parsed_conversion()->ProcessFormat( ConverterConsumer<Converter>(converter, args)); } else { return ParseFormatString(format.str(), ConverterConsumer<Converter>(converter, args)); } } class DefaultConverter { public: explicit DefaultConverter(FormatSinkImpl* sink) : sink_(sink) {} void Append(string_view s) const { sink_->Append(s); } bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const { return FormatArgImplFriend::Convert(*bound.arg(), bound, sink_); } private: FormatSinkImpl* sink_; }; class SummarizingConverter { public: explicit SummarizingConverter(FormatSinkImpl* sink) : sink_(sink) {} void Append(string_view s) const { sink_->Append(s); } bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const { UntypedFormatSpecImpl spec("%d"); std::ostringstream ss; ss << "{" << Streamable(spec, {*bound.arg()}) << ":" << FormatConversionSpecImplFriend::FlagsToString(bound); if (bound.width() >= 0) ss << bound.width(); if (bound.precision() >= 0) ss << "." << bound.precision(); ss << bound.conversion_char() << "}"; Append(ss.str()); return true; } private: FormatSinkImpl* sink_; }; } // namespace bool BindWithPack(const UnboundConversion* props, absl::Span<const FormatArgImpl> pack, BoundConversion* bound) { return ArgContext(pack).Bind(props, bound); } std::string Summarize(const UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args) { typedef SummarizingConverter Converter; std::string out; { // inner block to destroy sink before returning out. It ensures a last // flush. FormatSinkImpl sink(&out); if (!ConvertAll(format, args, Converter(&sink))) { return ""; } } return out; } bool FormatUntyped(FormatRawSinkImpl raw_sink, const UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args) { FormatSinkImpl sink(raw_sink); using Converter = DefaultConverter; return ConvertAll(format, args, Converter(&sink)); } std::ostream& Streamable::Print(std::ostream& os) const { if (!FormatUntyped(&os, format_, args_)) os.setstate(std::ios::failbit); return os; } std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args) { size_t orig = out->size(); if (ABSL_PREDICT_FALSE(!FormatUntyped(out, format, args))) { out->erase(orig); } return *out; } std::string FormatPack(const UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args) { std::string out; if (ABSL_PREDICT_FALSE(!FormatUntyped(&out, format, args))) { out.clear(); } return out; } int FprintF(std::FILE* output, const UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args) { FILERawSink sink(output); if (!FormatUntyped(&sink, format, args)) { errno = EINVAL; return -1; } if (sink.error()) { errno = sink.error(); return -1; } if (sink.count() > static_cast<size_t>(std::numeric_limits<int>::max())) { errno = EFBIG; return -1; } return static_cast<int>(sink.count()); } int SnprintF(char* output, size_t size, const UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args) { BufferRawSink sink(output, size ? size - 1 : 0); if (!FormatUntyped(&sink, format, args)) { errno = EINVAL; return -1; } size_t total = sink.total_written(); if (size) output[std::min(total, size - 1)] = 0; return static_cast<int>(total); } } // namespace str_format_internal ABSL_NAMESPACE_END } // namespace absl