Spicy
util.h
1 // Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
2 
3 #pragma once
4 
5 #if ! defined(_MSC_VER)
6 #include <cxxabi.h>
7 #endif
8 #ifndef _WIN32
9 #include <unistd.h>
10 #else
11 #include <io.h>
12 #include <process.h>
13 #endif
14 
15 #include <algorithm>
16 #include <cstdint>
17 #include <limits>
18 #include <list>
19 #include <memory>
20 #include <ranges>
21 #include <set>
22 #include <string>
23 #include <string_view>
24 #include <tuple>
25 #include <type_traits>
26 #include <utility>
27 #include <vector>
28 
29 #include <hilti/rt/3rdparty/ArticleEnumClass-v2/EnumClass.h>
30 #include <hilti/rt/exception.h>
31 #include <hilti/rt/filesystem.h>
32 #include <hilti/rt/macros.h>
33 #include <hilti/rt/result.h>
34 #include <hilti/rt/types/set_fwd.h>
35 #include <hilti/rt/types/time.h>
36 #include <hilti/rt/types/vector_fwd.h>
37 
39 #define HILTI_INTERNAL(id) _t_##id
40 
42 #define HILTI_INTERNAL_ID(id) "_t_" id
43 
45 #define HILTI_INTERNAL_NS hlt_internal
46 
48 #define HILTI_INTERNAL_NS_ID "hlt_internal"
49 
51 #define HILTI_INTERNAL_GLOBAL(id) hlt_internal_##id
52 
54 #define HILTI_INTERNAL_GLOBAL_ID(id) "hlt_internal_" id
55 
63 #define HILTI_RT_ENUM_TYPE(name, ...) \
64  struct name { \
65  enum Value : int64_t { Undef = -1, __VA_ARGS__ }; \
66  constexpr name(int64_t value = Undef) noexcept : _value(value) {} \
67  friend constexpr bool operator==(const name& a, const name& b) noexcept { return a.value() == b.value(); } \
68  friend constexpr bool operator!=(const name& a, const name& b) noexcept { return ! (a == b); } \
69  friend constexpr bool operator<(const name& a, const name& b) noexcept { return a.value() < b.value(); } \
70  constexpr int64_t value() const { return _value; } \
71  int64_t _value; \
72  }
73 
82 #define HILTI_RT_ENUM(name, ...) \
83  HILTI_RT_ENUM_TYPE(name, __VA_ARGS__); \
84  inline name Enum(name::Value value) { return name(value); }
85 
91 #if defined(__linux__)
92 #define HILTI_THREAD_LOCAL __thread
93 #else
94 #define HILTI_THREAD_LOCAL thread_local
95 #endif
96 
97 namespace hilti::rt {
98 
100 [[noreturn]] void internalError(std::string_view msg);
101 
102 } // namespace hilti::rt
103 
104 #undef TINYFORMAT_ERROR
105 #define TINYFORMAT_ERROR(reason) throw ::hilti::rt::FormattingError(reason)
106 #include <hilti/rt/3rdparty/tinyformat/tinyformat.h>
107 #include <hilti/rt/extension-points.h>
108 #include <hilti/rt/fmt.h>
109 
110 namespace hilti::rt {
111 
113 extern std::string version();
114 
116 [[noreturn]] extern void abort_with_backtrace();
117 
119 [[noreturn]] extern void cannot_be_reached();
120 
123  // Note when changing this, update `resource_usage()`.
124  double user_time; //< user time since runtime initialization
125  double system_time; //< system time since runtime initialization
126  uint64_t memory_heap; //< current size of heap in bytes
127  uint64_t num_fibers; //< number of fibers currently in use
128  uint64_t max_fibers; //< high-water mark for number of fibers in use
129  uint64_t max_fiber_stack_size; //< global high-water mark for fiber stack size
130  uint64_t cached_fibers; //< number of fibers currently cached for reuse
131 };
132 
135 
143 
145 hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path& p);
146 
152 inline std::string_view rtrim(std::string_view s, const std::string& chars) noexcept {
153  s.remove_suffix(s.size() -
154  [](size_t pos) { return pos != std::string_view::npos ? pos + 1 : 0; }(s.find_last_not_of(chars)));
155  return s;
156 }
157 
163 inline std::string_view ltrim(std::string_view s, const std::string& chars) noexcept {
164  s.remove_prefix(std::min(s.find_first_not_of(chars), s.size()));
165  return s;
166 }
167 
174 inline std::string_view trim(std::string_view s, const std::string& chars) noexcept {
175  return ltrim(rtrim(s, chars), chars);
176 }
177 
178 namespace detail {
179 constexpr char whitespace_chars[] = " \t\f\v\n\r";
180 } // namespace detail
181 
187 inline std::string_view rtrim(std::string_view s) noexcept { return rtrim(s, detail::whitespace_chars); }
188 
194 inline std::string_view ltrim(std::string_view s) noexcept { return ltrim(s, detail::whitespace_chars); }
195 
201 inline std::string_view trim(std::string_view s) noexcept { return trim(s, detail::whitespace_chars); }
202 
209 std::vector<std::string_view> split(std::string_view s, std::string_view delim);
210 
216 std::vector<std::string_view> split(std::string_view s);
217 
224 extern std::pair<std::string, std::string> split1(std::string s);
225 
232 extern std::pair<std::string, std::string> rsplit1(std::string s);
233 
240 extern std::pair<std::string, std::string> split1(std::string s, const std::string& delim);
241 
248 extern std::pair<std::string, std::string> rsplit1(std::string s, const std::string& delim);
249 
255 std::string replace(std::string s, std::string_view o, std::string_view n);
256 
262 bool startsWith(std::string_view s, std::string_view prefix);
263 
269 bool endsWith(std::string_view s, std::string_view suffix);
270 
275 template<typename T,
276  typename TIter = decltype(std::begin(std::declval<T>())),
277  typename = decltype(std::end(std::declval<T>()))>
278 constexpr auto enumerate(T&& iterable) {
279  // TODO(C++23): replace callers with `std::views::enumerate` in C++23 and remove this function.
280  struct iterator {
281  size_t i;
282  TIter iter;
283  bool operator!=(const iterator& other) const { return iter != other.iter; }
284  void operator++() {
285  ++i;
286  ++iter;
287  }
288  auto operator*() const { return std::tie(i, *iter); }
289  };
290  struct iterable_wrapper {
291  T iterable;
292  auto begin() { return iterator{0, std::begin(iterable)}; }
293  auto end() { return iterator{0, std::end(iterable)}; }
294  };
295  return iterable_wrapper{std::forward<T>(iterable)};
296 }
297 
298 /*
299  * Expands escape sequences in a UTF8 string. The following escape sequences
300  * are supported:
301  *
302  * ============ ============================
303  * Escape Result
304  * ============ ============================
305  * \\ Backslash
306  * \\n Line feed
307  * \\r Carriage return
308  * \\t Tabulator
309  * \\uXXXX 16-bit Unicode codepoint
310  * \\UXXXXXXXX 32-bit Unicode codepoint
311  * \\xXX 8-bit hex value
312  * ============ ============================
313  *
314  * @param str string to expand
315  * @return A UTF8 string with escape sequences expanded
316  */
317 std::string expandUTF8Escapes(std::string s);
318 
319 namespace render_style {
320 
327 enum class Bytes {
328  Default = 0,
329  EscapeQuotes = (1U << 1U),
330  UseOctal = (1U << 2U),
331  NoEscapeBackslash = (1U << 3U),
332 };
333 
342 enum class UTF8 {
343  Default = 0,
344  EscapeQuotes = (1U << 1U),
345  NoEscapeBackslash = (1U << 2U),
346  NoEscapeControl = (1U << 3U),
347  NoEscapeHex =
348  (1U << 4U),
349 };
350 
351 } // namespace render_style
352 
353 } // namespace hilti::rt
354 
355 enableEnumClassBitmask(hilti::rt::render_style::Bytes); // must be in global scope
356 enableEnumClassBitmask(hilti::rt::render_style::UTF8); // must be in global scope
357 
358 namespace hilti::rt {
359 
360 /*
361  * Escapes non-printable characters in a raw string. This produces a new
362  * string that can be reverted by expandEscapes().
363  *
364  * @param str string to escape
365  * @param escape_quotes if true, also escapes quotes characters
366  * @param use_octal use `\NNN` instead of `\XX` (needed for C++)
367  * @return escaped string
368  */
369 std::string escapeBytes(std::string_view s, bitmask<render_style::Bytes> style = render_style::Bytes::Default);
370 
371 /*
372  * Escapes non-printable and control characters in an UTF8 string. This
373  * produces a new string that can be reverted by expandEscapes().
374  *
375  * @param str string to escape
376  * @param escape_quotes if true, also escapes quotes characters
377  * @param escape_control if false, do not escape control characters
378  * @param keep_hex if true, do not escape our custom "\xYY" escape codes
379  * @return escaped std::string
380  */
381 std::string escapeUTF8(std::string_view s, bitmask<render_style::UTF8> style = render_style::UTF8::Default);
382 
387 template<std::ranges::input_range T>
388 std::string join(T&& l, std::string_view delim = "")
389  requires(std::is_constructible_v<std::string, std::ranges::range_value_t<T>>)
390 {
391  std::string result;
392  bool first = true;
393 
394  for ( const auto& i : l ) {
395  if ( ! first )
396  result.append(delim);
397 
398  result.append(i);
399  first = false;
400  }
401 
402  return result;
403 }
404 
405 namespace detail {
406 
408 template<typename T>
409 struct is_Vector : std::false_type {};
410 
411 template<typename T, typename Allocator>
412 struct is_Vector<Vector<T, Allocator>> : std::true_type {};
413 
416 template<typename C, typename Y>
417 constexpr auto transform_result_value(const C&) {
418  using X = typename C::value_type;
419 
420  if constexpr ( std::is_same_v<C, std::vector<X>> ) {
421  return std::vector<Y>();
422  }
423  else if constexpr ( std::is_same_v<C, std::set<X>> ) {
424  return std::set<Y>();
425  }
426  else if constexpr ( is_Vector<C>::value ) {
427  // We do not preserve the allocator since a proper custom one could depend on `Y`.
428  return Vector<Y>();
429  }
430  else if constexpr ( std::is_same_v<C, Set<X>> ) {
431  return Set<Y>();
432  }
433  else
434  return std::vector<Y>(); // fallback
435 }
436 
437 } // namespace detail
438 
439 class OutOfRange;
440 
463 template<class Iter, typename Result>
464 inline Iter atoi_n(Iter s, Iter e, uint8_t base, Result* result)
465  requires std::is_integral_v<Result> && (sizeof(Result) <= sizeof(uint64_t)) && std::contiguous_iterator<Iter>
466 {
467  if ( base < 2 || base > 36 )
468  throw OutOfRange("base for numerical conversion must be between 2 and 36");
469 
470  if ( s == e )
471  throw InvalidArgument("cannot decode from empty range");
472 
473  bool neg = false;
474  auto it = s;
475 
476  if ( *it == '-' ) {
477  neg = true;
478  ++it;
479  }
480  else if ( *it == '+' ) {
481  neg = false;
482  ++it;
483  }
484 
485  // Compute range of possible values. For signed types the absolute minimum
486  // value typically does not fit into the type, so instead store the maximum
487  // value in the corresponding unsigned type which has enough range.
488  constexpr auto max = std::numeric_limits<Result>::max();
489  constexpr auto min = std::numeric_limits<Result>::min();
490 
491  // Sanity check since e.g., our own `hilti::rt::integer::safe` type does
492  // not implement `std::is_signed_v`, `std::make_unsigned_t`, or
493  // `std::numeric_limits` so `min` or `max` could be the default value for
494  // that type. Since we require a builtin integral type above we should
495  // never reach this.
496  static_assert(min < max);
497 
498  using Unsigned = std::conditional_t<std::is_signed_v<Result>, std::make_unsigned_t<Result>, Result>;
499  constexpr auto max_value =
500  std::is_unsigned_v<Result> ? Unsigned(max) : static_cast<Unsigned>(0) - static_cast<Unsigned>(min);
501 
502  // Since we handle any sign above we can accumulate into an unsigned
503  // integer when iterating digits. We pick `uint64_t` since it is large
504  // enough to handle any type we need to handle.
505  static_assert(max_value <= std::numeric_limits<uint64_t>::max());
506  uint64_t n = 0;
507  for ( ; it != e; ++it ) {
508  auto c = *it;
509 
510  // `uint8_t` is sufficient to store any digit up to max base 36.
511  uint8_t d;
512  if ( c >= '0' && c < '0' + base )
513  d = c - '0';
514  else if ( c >= 'a' && c < 'a' - 10 + base )
515  d = c - 'a' + 10;
516  else if ( c >= 'A' && c < 'A' - 10 + base )
517  d = c - 'A' + 10;
518  else
519  break;
520 
521  if ( n > static_cast<uint64_t>(max_value / base) || d > max_value - (n * base) )
522  throw OutOfRange("cannot decode value in chosen base");
523 
524  n = (n * base) + d;
525  }
526 
527  if ( it == s )
528  return s;
529 
530  s = it;
531 
532  // Convert to and store in target value. We have already checked the range above.
533  if ( neg )
534  *result = static_cast<Result>(0ULL - n);
535  else
536  *result = static_cast<Result>(n);
537 
538  return s;
539 }
540 
544 template<typename I1, typename I2>
545 inline I1 pow(I1 base, I2 exp) {
546  I1 x = 1;
547 
548  while ( true ) {
549  if ( exp & 1 )
550  x *= base;
551 
552  exp >>= 1;
553  if ( ! exp )
554  break;
555  base *= base;
556  }
557 
558  return x;
559 }
560 
561 // Tuple for-each, from
562 // https://stackoverflow.com/questions/40212085/type-erasure-for-objects-containing-a-stdtuple-in-c11
563 namespace detail {
564 template<typename T, typename F, std::size_t... Is>
565 constexpr auto map_tuple(T&& tup, F& f, std::index_sequence<Is...> /*unused*/) {
566  return std::make_tuple(f(std::get<Is>(std::forward<T>(tup)))...);
567 }
568 } // namespace detail
569 
571 template<typename F, std::size_t I = 0, typename... Ts>
572 void tuple_for_each(const std::tuple<Ts...>& tup, F func) {
573  if constexpr ( I == sizeof...(Ts) )
574  return;
575  else {
576  func(std::get<I>(tup));
577  tuple_for_each<F, I + 1>(tup, func);
578  }
579 }
580 
585 template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
586 constexpr auto map_tuple(T&& tup, F f) {
587  return detail::map_tuple(std::forward<T>(tup), f, std::make_index_sequence<TupSize>{});
588 }
589 
591 HILTI_RT_ENUM(ByteOrder, Little, Big, Network, Host);
592 
597 extern ByteOrder systemByteOrder();
598 
599 namespace detail::adl {
600 std::string to_string(const ByteOrder& x, tag /*unused*/);
601 }
602 
615 Time strptime(std::string_view buf, std::string_view format);
616 
617 // RAII helper to create a temporary directory.
619 public:
621  const auto tmpdir = hilti::rt::filesystem::temp_directory_path();
622 
623 #if defined(_WIN32)
624  auto path = (tmpdir / "hilti-rt-test-XXXXXX").string();
625  if ( _mktemp_s(path.data(), path.size() + 1) != 0 )
626  throw RuntimeError("cannot create temporary directory");
627 
628  if ( ! hilti::rt::filesystem::create_directory(path) )
629  throw RuntimeError("cannot create temporary directory");
630 #else
631  auto template_ = (tmpdir / "hilti-rt-test-XXXXXX").native();
632  auto* path = ::mkdtemp(template_.data());
633  if ( ! path )
634  throw RuntimeError("cannot create temporary directory");
635 #endif
636 
637  _path = path;
638  }
639 
640  TemporaryDirectory(const TemporaryDirectory& other) = delete;
641  TemporaryDirectory(TemporaryDirectory&& other) noexcept { _path = std::move(other._path); }
642 
643  ~TemporaryDirectory() {
644  // In general, ignore errors in this function.
645  std::error_code ec;
646 
647  if ( ! hilti::rt::filesystem::exists(_path, ec) )
648  return;
649 
650  // Make sure we have permissions to remove the directory.
651  hilti::rt::filesystem::permissions(_path, hilti::rt::filesystem::perms::all, ec);
652 
653  // The desugared loop contains an iterator increment which could throw (no automagic call of
654  // `std::filesystem::recursive_directory_iterator::increment`), see LWG3013 for the "fix".
655  // Ignore errors from that.
656  try {
657  for ( const auto& entry : hilti::rt::filesystem::recursive_directory_iterator(_path, ec) )
658  hilti::rt::filesystem::permissions(entry, hilti::rt::filesystem::perms::all, ec);
659  } catch ( ... ) {
660  ; // Ignore error.
661  }
662 
663  hilti::rt::filesystem::remove_all(_path, ec); // ignore errors
664  }
665 
666  const auto& path() const { return _path; }
667 
668  TemporaryDirectory& operator=(const TemporaryDirectory& other) = delete;
669  TemporaryDirectory& operator=(TemporaryDirectory&& other) noexcept {
670  _path = std::move(other._path);
671  return *this;
672  }
673 
674 private:
675  hilti::rt::filesystem::path _path;
676 };
677 
678 // Combine two or more hashes.
679 template<typename... Hashes>
680 constexpr std::size_t hashCombine(std::size_t hash1, std::size_t hash2, Hashes... hashes) {
681  auto result = hash1 ^ (hash2 << 1);
682 
683  if constexpr ( sizeof...(hashes) > 0 )
684  return hashCombine(result, hashes...);
685  else
686  return result;
687 }
688 
689 namespace control {
690 
691 template<typename Data, typename Error>
692 class Reference;
693 
705 template<typename Data, typename Error>
706 class Block {
707 public:
712 
713  Block() = default;
714  Block(Data* data) : _data(data) {}
715 
716  Block(const Block& other) : Block(other._data) {}
717  Block(Block&&) = default;
718 
719  Block& operator=(const Block& other) {
720  if ( this != &other ) {
721  _data = other._data;
722  _control.reset();
723  }
724 
725  return *this;
726  }
727 
728  Block& operator=(Block&&) = default;
729 
730  friend bool operator==(const Block& a, const Block& b) {
731  return std::tie(a._control, a._data) == std::tie(b._control, b._data);
732  }
733 
734  friend bool operator!=(const Block& a, const Block& b) { return ! (a == b); }
735 
739  /* implicit */ operator Ref() const {
740  if ( ! _control )
741  _control = std::make_shared<bool>();
742 
743  return {_control, _data};
744  }
745 
749  void Reset() { _control.reset(); }
750 
751 private:
752  mutable std::shared_ptr<void> _control;
753  Data* _data = nullptr;
754 };
755 
762 template<typename Data, typename Error>
763 class Reference {
764 public:
765  Reference() = default;
766  Reference(const Reference&) = default;
767  Reference(Reference&&) = default;
768 
769  Reference& operator=(const Reference&) = default;
770  Reference& operator=(Reference&&) = default;
771 
775  bool isValid() const { return _data && ! _control.expired(); }
776 
782  const Data& get() const {
783  if ( ! _data )
784  throw Error("underlying object is invalid");
785 
786  if ( _control.expired() )
787  throw Error("underlying object has expired");
788 
789  return *_data;
790  }
791 
797  Data& get() {
798  if ( ! _data )
799  throw Error("underlying object is invalid");
800 
801  if ( _control.expired() )
802  throw Error("underlying object has expired");
803 
804  return *_data;
805  }
806 
807  friend bool operator==(const Reference& a, const Reference& b) {
808  return ! a._control.owner_before(b._control) && ! b._control.owner_before(a._control);
809  }
810 
811  friend bool operator!=(const Reference& a, const Reference& b) { return ! (a == b); }
812 
813 private:
814  template<typename T, typename E>
815  friend class Block;
816 
817  Reference(std::weak_ptr<void> control, Data* data) : _control(std::move(control)), _data(data) {}
818 
819  std::weak_ptr<void> _control;
820  Data* _data = nullptr;
821 };
822 
823 } // namespace control
824 
828 template<typename EF>
829 struct scope_exit {
830  scope_exit(EF&& f) noexcept : _f(std::forward<EF>(f)) {}
831 
832  scope_exit(const scope_exit&) = delete;
833  scope_exit(scope_exit&&) = delete;
834 
835  ~scope_exit() noexcept {
836  try {
837  _f();
838  } catch ( ... ) {
839  // Ignore.
840  }
841  }
842 
843  EF _f;
844 };
845 
846 } // namespace hilti::rt
Definition: network.h:19
Definition: result.h:73
Definition: util.h:618
Definition: vector.h:281
Definition: util.h:706
void Reset()
Definition: util.h:749
Definition: util.h:763
Data & get()
Definition: util.h:797
const Data & get() const
Definition: util.h:782
bool isValid() const
Definition: util.h:775
Definition: any.h:7
void tuple_for_each(const std::tuple< Ts... > &tup, F func)
Definition: util.h:572
std::vector< std::string_view > split(std::string_view s, std::string_view delim)
Definition: util.cc:116
constexpr auto enumerate(T &&iterable)
Definition: util.h:278
bool endsWith(std::string_view s, std::string_view suffix)
Definition: util.cc:401
hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path &p)
Definition: util.cc:106
hilti::rt::Result< hilti::rt::filesystem::path > createTemporaryFile(const std::string &prefix="")
Definition: util.cc:79
std::pair< std::string, std::string > split1(std::string s)
Definition: util.cc:160
std::string_view trim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:174
ResourceUsage resource_usage()
Definition: util.cc:55
std::string replace(std::string s, std::string_view o, std::string_view n)
Definition: util.cc:386
std::string version()
Definition: util.cc:33
std::string join(T &&l, std::string_view delim="") requires(std
Definition: util.h:388
void cannot_be_reached()
Definition: util.cc:53
Iter atoi_n(Iter s, Iter e, uint8_t base, Result *result) requires std
Definition: util.h:464
std::string_view rtrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:152
constexpr auto map_tuple(T &&tup, F f)
Definition: util.h:586
std::pair< std::string, std::string > rsplit1(std::string s)
Definition: util.cc:167
std::string_view ltrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:163
std::string to_string(T &&x)
Definition: extension-points.h:26
ByteOrder systemByteOrder()
Definition: util.cc:408
bool startsWith(std::string_view s, std::string_view prefix)
Definition: util.cc:399
void internalError(std::string_view msg)
Definition: logging.cc:16
Time strptime(std::string_view buf, std::string_view format)
Definition: util.cc:433
void abort_with_backtrace()
Definition: util.cc:45
Definition: util.h:122
Definition: util.h:409
Definition: util.h:829