Spicy
util.h
1 // Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
2 
3 #pragma once
4 
5 #include <cxxabi.h>
6 #include <unistd.h>
7 
8 #include <algorithm>
9 #include <list>
10 #include <memory>
11 #include <optional>
12 #include <set>
13 #include <string>
14 #include <string_view>
15 #include <tuple>
16 #include <utility>
17 #include <vector>
18 
19 #include <hilti/rt/3rdparty/ArticleEnumClass-v2/EnumClass.h>
20 #include <hilti/rt/exception.h>
21 #include <hilti/rt/filesystem.h>
22 #include <hilti/rt/result.h>
23 #include <hilti/rt/types/set_fwd.h>
24 #include <hilti/rt/types/time.h>
25 #include <hilti/rt/types/vector_fwd.h>
26 
39 #define HILTI_RT_ENUM_WITH_DEFAULT(name, default_, ...) \
40  struct name { \
41  enum Value : int64_t { __VA_ARGS__ }; \
42  constexpr name(int64_t value = default_) noexcept : _value(value) {} \
43  friend name Enum(Value value) { return name(value); } \
44  friend constexpr bool operator==(const name& a, const name& b) noexcept { return a.value() == b.value(); } \
45  friend constexpr bool operator!=(const name& a, const name& b) noexcept { return ! (a == b); } \
46  friend constexpr bool operator<(const name& a, const name& b) noexcept { return a.value() < b.value(); } \
47  constexpr int64_t value() const { return _value; } \
48  int64_t _value; \
49  }
50 
63 #define HILTI_RT_ENUM(name, ...) HILTI_RT_ENUM_WITH_DEFAULT(name, Undef, __VA_ARGS__)
64 
65 
71 #if defined(__linux__)
72 #define HILTI_THREAD_LOCAL __thread
73 #else
74 #define HILTI_THREAD_LOCAL thread_local
75 #endif
76 
77 namespace hilti::rt {
78 
80 void internalError(std::string_view msg) __attribute__((noreturn));
81 
82 } // namespace hilti::rt
83 
84 #undef TINYFORMAT_ERROR
85 #define TINYFORMAT_ERROR(reason) throw ::hilti::rt::FormattingError(reason)
86 #include <hilti/rt/3rdparty/tinyformat/tinyformat.h>
87 #include <hilti/rt/extension-points.h>
88 #include <hilti/rt/fmt.h>
89 
90 namespace hilti::rt {
91 
93 extern std::string version();
94 
96 extern void abort_with_backtrace() __attribute__((noreturn));
97 
99 extern void cannot_be_reached() __attribute__((noreturn));
100 
103  // Note when changing this, update `resource_usage()`.
104  double user_time; //< user time since runtime initialization
105  double system_time; //< system time since runtime initialization
106  uint64_t memory_heap; //< current size of heap in bytes
107  uint64_t num_fibers; //< number of fibers currently in use
108  uint64_t max_fibers; //< high-water mark for number of fibers in use
109  uint64_t max_fiber_stack_size; //< global high-water mark for fiber stack size
110  uint64_t cached_fibers; //< number of fibers currently cached for reuse
111 };
112 
115 
117 extern std::optional<std::string> getenv(const std::string& name);
118 
126 
128 hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path& p);
129 
135 inline std::string_view rtrim(std::string_view s, const std::string& chars) noexcept {
136  s.remove_suffix(s.size() -
137  [](size_t pos) { return pos != std::string_view::npos ? pos + 1 : 0; }(s.find_last_not_of(chars)));
138  return s;
139 }
140 
146 inline std::string_view ltrim(std::string_view s, const std::string& chars) noexcept {
147  s.remove_prefix(std::min(s.find_first_not_of(chars), s.size()));
148  return s;
149 }
150 
157 inline std::string_view trim(std::string_view s, const std::string& chars) noexcept {
158  return ltrim(rtrim(s, chars), chars);
159 }
160 
161 namespace detail {
162 constexpr char whitespace_chars[] = " \t\f\v\n\r";
163 } // namespace detail
164 
170 inline std::string_view rtrim(std::string_view s) noexcept { return rtrim(s, detail::whitespace_chars); }
171 
177 inline std::string_view ltrim(std::string_view s) noexcept { return ltrim(s, detail::whitespace_chars); }
178 
184 inline std::string_view trim(std::string_view s) noexcept { return trim(s, detail::whitespace_chars); }
185 
192 std::vector<std::string_view> split(std::string_view s, std::string_view delim);
193 
199 std::vector<std::string_view> split(std::string_view s);
200 
207 extern std::pair<std::string, std::string> split1(std::string s);
208 
215 extern std::pair<std::string, std::string> rsplit1(std::string s);
216 
223 extern std::pair<std::string, std::string> split1(std::string s, const std::string& delim);
224 
231 extern std::pair<std::string, std::string> rsplit1(std::string s, const std::string& delim);
232 
238 std::string replace(std::string s, std::string_view o, std::string_view n);
239 
245 bool startsWith(std::string_view s, std::string_view prefix);
246 
252 bool endsWith(std::string_view s, std::string_view suffix);
253 
258 template<typename T, typename TIter = decltype(std::begin(std::declval<T>())),
259  typename = decltype(std::end(std::declval<T>()))>
260 constexpr auto enumerate(T&& iterable) {
261  struct iterator {
262  size_t i;
263  TIter iter;
264  bool operator!=(const iterator& other) const { return iter != other.iter; }
265  void operator++() {
266  ++i;
267  ++iter;
268  }
269  auto operator*() const { return std::tie(i, *iter); }
270  };
271  struct iterable_wrapper {
272  T iterable;
273  auto begin() { return iterator{0, std::begin(iterable)}; }
274  auto end() { return iterator{0, std::end(iterable)}; }
275  };
276  return iterable_wrapper{std::forward<T>(iterable)};
277 }
278 
279 /*
280  * Expands escape sequences in a UTF8 string. The following escape sequences
281  * are supported:
282  *
283  * ============ ============================
284  * Escape Result
285  * ============ ============================
286  * \\ Backslash
287  * \\n Line feed
288  * \\r Carriage return
289  * \\t Tabulator
290  * \\uXXXX 16-bit Unicode codepoint
291  * \\UXXXXXXXX 32-bit Unicode codepoint
292  * \\xXX 8-bit hex value
293  * ============ ============================
294  *
295  * @param str string to expand
296  * @return A UTF8 string with escape sequences expanded
297  */
298 std::string expandUTF8Escapes(std::string s);
299 
300 namespace render_style {
301 
308 enum class Bytes {
309  Default = 0,
310  EscapeQuotes = (1U << 1U),
311  UseOctal = (1U << 2U),
312  NoEscapeBackslash = (1U << 3U),
313 };
314 
323 enum class UTF8 {
324  Default = 0,
325  EscapeQuotes = (1U << 1U),
326  NoEscapeBackslash = (1U << 2U),
327  NoEscapeControl = (1U << 3U),
328  NoEscapeHex =
329  (1U << 4U),
330 };
331 
332 } // namespace render_style
333 
334 } // namespace hilti::rt
335 
336 enableEnumClassBitmask(hilti::rt::render_style::Bytes); // must be in global scope
337 enableEnumClassBitmask(hilti::rt::render_style::UTF8); // must be in global scope
338 
339 namespace hilti::rt {
340 
341 /*
342  * Escapes non-printable characters in a raw string. This produces a new
343  * string that can be reverted by expandEscapes().
344  *
345  * @param str string to escape
346  * @param escape_quotes if true, also escapes quotes characters
347  * @param use_octal use `\NNN` instead of `\XX` (needed for C++)
348  * @return escaped string
349  */
350 std::string escapeBytes(std::string_view s, bitmask<render_style::Bytes> style = render_style::Bytes::Default);
351 
352 /*
353  * Escapes non-printable and control characters in an UTF8 string. This
354  * produces a new string that can be reverted by expandEscapes().
355  *
356  * @param str string to escape
357  * @param escape_quotes if true, also escapes quotes characters
358  * @param escape_control if false, do not escape control characters
359  * @param keep_hex if true, do not escape our custom "\xYY" escape codes
360  * @return escaped std::string
361  */
362 std::string escapeUTF8(std::string_view s, bitmask<render_style::UTF8> style = render_style::UTF8::Default);
363 
368 template<typename T>
369 std::string join(const T& l, const std::string& delim = "") {
370  std::string result;
371  bool first = true;
372 
373  for ( const auto& i : l ) {
374  if ( not first )
375  result += delim;
376  result += std::string(i);
377  first = false;
378  }
379 
380  return result;
381 }
382 
383 namespace detail {
384 
386 template<typename T>
387 struct is_Vector : std::false_type {};
388 
389 template<typename T, typename Allocator>
390 struct is_Vector<Vector<T, Allocator>> : std::true_type {};
391 
394 template<typename C, typename Y>
395 constexpr auto transform_result_value(const C&) {
396  using X = typename C::value_type;
397 
398  if constexpr ( std::is_same_v<C, std::vector<X>> ) {
399  return std::vector<Y>();
400  }
401  else if constexpr ( std::is_same_v<C, std::set<X>> ) {
402  return std::set<Y>();
403  }
404  else if constexpr ( is_Vector<C>::value ) {
405  // We do not preserve the allocator since a proper custom one could depend on `Y`.
406  return Vector<Y>();
407  }
408  else if constexpr ( std::is_same_v<C, Set<X>> ) {
409  return Set<Y>();
410  }
411  else
412  return std::vector<Y>(); // fallback
413 }
414 
415 } // namespace detail
416 
418 template<typename C, typename F>
419 auto transform(const C& x, F f) {
420  using Y = typename std::invoke_result_t<F, typename C::value_type&>;
421 
422  auto y = detail::transform_result_value<C, Y>(x);
423  std::transform(std::begin(x), std::end(x), std::inserter(y, std::end(y)), f);
424 
425  return y;
426 }
427 
428 class OutOfRange;
429 
452 template<class Iter, typename Result>
453 inline Iter atoi_n(Iter s, Iter e, uint8_t base, Result* result) {
454  if ( base < 2 || base > 36 )
455  throw OutOfRange("base for numerical conversion must be between 2 and 36");
456 
457  if ( s == e )
458  throw InvalidArgument("cannot decode from empty range");
459 
460  std::optional<Result> n = std::nullopt;
461  bool neg = false;
462  auto it = s;
463 
464  if ( *it == '-' ) {
465  neg = true;
466  ++it;
467  }
468  else if ( *it == '+' ) {
469  neg = false;
470  ++it;
471  }
472 
473  for ( ; it != e; ++it ) {
474  auto c = *it;
475 
476  Result d;
477  if ( c >= '0' && c < '0' + base )
478  d = c - '0';
479  else if ( c >= 'a' && c < 'a' - 10 + base )
480  d = c - 'a' + 10;
481  else if ( c >= 'A' && c < 'A' - 10 + base )
482  d = c - 'A' + 10;
483  else
484  break;
485 
486  n = n.value_or(Result()) * base + d;
487  }
488 
489  if ( ! n )
490  return s;
491 
492  s = it;
493 
494  if ( neg )
495  *result = -*n;
496  else
497  *result = *n;
498 
499  return s;
500 }
501 
505 template<typename I1, typename I2>
506 inline I1 pow(I1 base, I2 exp) {
507  I1 x = 1;
508 
509  while ( true ) {
510  if ( exp & 1 )
511  x *= base;
512 
513  exp >>= 1;
514  if ( ! exp )
515  break;
516  base *= base;
517  }
518 
519  return x;
520 }
521 
522 // Tuple for-each, from
523 // https://stackoverflow.com/questions/40212085/type-erasure-for-objects-containing-a-stdtuple-in-c11
524 namespace detail {
525 template<typename T, typename F, std::size_t... Is>
526 constexpr auto map_tuple(T&& tup, F& f, std::index_sequence<Is...> /*unused*/) {
527  return std::make_tuple(f(std::get<Is>(std::forward<T>(tup)))...);
528 }
529 } // namespace detail
530 
532 template<typename F, std::size_t I = 0, typename... Ts>
533 void tuple_for_each(const std::tuple<Ts...>& tup, F func) {
534  if constexpr ( I == sizeof...(Ts) )
535  return;
536  else {
537  func(std::get<I>(tup));
538  tuple_for_each<F, I + 1>(tup, func);
539  }
540 }
541 
546 template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
547 constexpr auto map_tuple(T&& tup, F f) {
548  return detail::map_tuple(std::forward<T>(tup), f, std::make_index_sequence<TupSize>{});
549 }
550 
552 HILTI_RT_ENUM(ByteOrder, Little, Big, Network, Host, Undef = -1);
553 
558 extern ByteOrder systemByteOrder();
559 
560 namespace detail::adl {
561 std::string to_string(const ByteOrder& x, tag /*unused*/);
562 }
563 
576 std::string strftime(const std::string& format, const Time& time);
577 
590 Time strptime(const std::string& buf, const std::string& format);
591 
592 // RAII helper to create a temporary directory.
594 public:
596  const auto tmpdir = hilti::rt::filesystem::temp_directory_path();
597  auto template_ = (tmpdir / "hilti-rt-test-XXXXXX").native();
598  auto* path = ::mkdtemp(template_.data());
599  if ( ! path )
600  throw RuntimeError("cannot create temporary directory");
601 
602  _path = path;
603  }
604 
605  TemporaryDirectory(const TemporaryDirectory& other) = delete;
606  TemporaryDirectory(TemporaryDirectory&& other) noexcept { _path = std::move(other._path); }
607 
608  ~TemporaryDirectory() {
609  // In general, ignore errors in this function.
610  std::error_code ec;
611 
612  if ( ! hilti::rt::filesystem::exists(_path, ec) )
613  return;
614 
615  // Make sure we have permissions to remove the directory.
616  hilti::rt::filesystem::permissions(_path, hilti::rt::filesystem::perms::all, ec);
617 
618  // The desugared loop contains an iterator increment which could throw (no automagic call of
619  // `std::filesystem::recursive_directory_iterator::increment`), see LWG3013 for the "fix".
620  // Ignore errors from that.
621  try {
622  for ( const auto& entry : hilti::rt::filesystem::recursive_directory_iterator(_path, ec) )
623  hilti::rt::filesystem::permissions(entry, hilti::rt::filesystem::perms::all, ec);
624  } catch ( ... ) {
625  ; // Ignore error.
626  }
627 
628  hilti::rt::filesystem::remove_all(_path, ec); // ignore errors
629  }
630 
631  const auto& path() const { return _path; }
632 
633  TemporaryDirectory& operator=(const TemporaryDirectory& other) = delete;
634  TemporaryDirectory& operator=(TemporaryDirectory&& other) noexcept {
635  _path = std::move(other._path);
636  return *this;
637  }
638 
639 private:
640  hilti::rt::filesystem::path _path;
641 };
642 
643 // Combine two or more hashes.
644 template<typename... Hashes>
645 constexpr std::size_t hashCombine(std::size_t hash1, std::size_t hash2, Hashes... hashes) {
646  auto result = hash1 ^ (hash2 << 1);
647 
648  if constexpr ( sizeof...(hashes) > 0 )
649  return hashCombine(result, hashes...);
650  else
651  return result;
652 }
653 
654 namespace control {
655 
656 template<typename Data, typename Error>
657 class Reference;
658 
670 template<typename Data, typename Error>
671 class Block {
672 public:
677 
678  Block() = default;
679  Block(Data* data) : _data(data) {}
680 
681  Block(const Block& other) : Block(other._data) {}
682  Block(Block&&) = default;
683 
684  Block& operator=(const Block& other) {
685  if ( this != &other ) {
686  _data = other._data;
687  _control.reset();
688  }
689 
690  return *this;
691  }
692 
693  Block& operator=(Block&&) = default;
694 
695  friend bool operator==(const Block& a, const Block& b) {
696  return std::tie(a._control, a._data) == std::tie(b._control, b._data);
697  }
698 
699  friend bool operator!=(const Block& a, const Block& b) { return ! (a == b); }
700 
704  /* implicit */ operator Ref() const {
705  if ( ! _control )
706  _control = std::make_shared<bool>();
707 
708  return {_control, _data};
709  }
710 
714  void Reset() { _control.reset(); }
715 
716 private:
717  mutable std::shared_ptr<void> _control;
718  Data* _data = nullptr;
719 };
720 
727 template<typename Data, typename Error>
728 class Reference {
729 public:
730  Reference() = default;
731  Reference(const Reference&) = default;
732  Reference(Reference&&) = default;
733 
734  Reference& operator=(const Reference&) = default;
735  Reference& operator=(Reference&&) = default;
736 
740  bool isValid() const { return _data && ! _control.expired(); }
741 
747  const Data& get() const {
748  if ( ! _data )
749  throw Error("underlying object is invalid");
750 
751  if ( _control.expired() )
752  throw Error("underlying object has expired");
753 
754  return *_data;
755  }
756 
762  Data& get() {
763  if ( ! _data )
764  throw Error("underlying object is invalid");
765 
766  if ( _control.expired() )
767  throw Error("underlying object has expired");
768 
769  return *_data;
770  }
771 
772  friend bool operator==(const Reference& a, const Reference& b) {
773  return ! a._control.owner_before(b._control) && ! b._control.owner_before(a._control);
774  }
775 
776  friend bool operator!=(const Reference& a, const Reference& b) { return ! (a == b); }
777 
778 private:
779  template<typename T, typename E>
780  friend class Block;
781 
782  Reference(std::weak_ptr<void> control, Data* data) : _control(std::move(control)), _data(data) {}
783 
784  std::weak_ptr<void> _control;
785  Data* _data = nullptr;
786 };
787 
788 } // namespace control
789 
793 template<typename EF>
794 struct scope_exit {
795  scope_exit(EF&& f) noexcept : _f(std::forward<EF>(f)) {}
796 
797  scope_exit(const scope_exit&) = delete;
798  scope_exit(scope_exit&&) = delete;
799 
800  ~scope_exit() noexcept {
801  try {
802  _f();
803  } catch ( ... ) {
804  // Ignore.
805  }
806  }
807 
808  EF _f;
809 };
810 
811 } // namespace hilti::rt
Definition: network.h:19
Definition: result.h:71
Definition: util.h:593
Definition: vector.h:260
Definition: util.h:671
void Reset()
Definition: util.h:714
Definition: util.h:728
Data & get()
Definition: util.h:762
const Data & get() const
Definition: util.h:747
bool isValid() const
Definition: util.h:740
Definition: any.h:7
void tuple_for_each(const std::tuple< Ts... > &tup, F func)
Definition: util.h:533
auto transform(const C &x, F f)
Definition: util.h:419
std::vector< std::string_view > split(std::string_view s, std::string_view delim)
Definition: util.cc:106
constexpr auto enumerate(T &&iterable)
Definition: util.h:260
std::optional< std::string > getenv(const std::string &name)
Definition: util.cc:71
bool endsWith(std::string_view s, std::string_view suffix)
Definition: util.cc:390
hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path &p)
Definition: util.cc:96
Time strptime(const std::string &buf, const std::string &format)
Definition: util.cc:445
void internalError(std::string_view msg) __attribute__((noreturn))
Definition: logging.cc:16
hilti::rt::Result< hilti::rt::filesystem::path > createTemporaryFile(const std::string &prefix="")
Definition: util.cc:78
std::pair< std::string, std::string > split1(std::string s)
Definition: util.cc:150
std::string_view trim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:157
ResourceUsage resource_usage()
Definition: util.cc:47
std::string replace(std::string s, std::string_view o, std::string_view n)
Definition: util.cc:375
std::string strftime(const std::string &format, const Time &time)
Definition: util.cc:419
std::string version()
Definition: util.cc:25
Iter atoi_n(Iter s, Iter e, uint8_t base, Result *result)
Definition: util.h:453
I1 pow(I1 base, I2 exp)
Definition: util.h:506
std::string_view rtrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:135
void abort_with_backtrace() __attribute__((noreturn))
Definition: util.cc:37
constexpr auto map_tuple(T &&tup, F f)
Definition: util.h:547
std::pair< std::string, std::string > rsplit1(std::string s)
Definition: util.cc:157
std::string_view ltrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:146
std::string join(const T &l, const std::string &delim="")
Definition: util.h:369
std::string to_string(T &&x)
Definition: extension-points.h:26
ByteOrder systemByteOrder()
Definition: util.cc:397
bool startsWith(std::string_view s, std::string_view prefix)
Definition: util.cc:388
void cannot_be_reached() __attribute__((noreturn))
Definition: util.cc:45
Definition: util.h:102
Definition: util.h:387
Definition: util.h:794