]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
fix: Pass unsigned char to std::is* and std::tolower
authorJoel Rosdahl <joel@rosdahl.net>
Mon, 20 Oct 2025 19:04:19 +0000 (21:04 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sat, 25 Oct 2025 06:49:49 +0000 (08:49 +0200)
Passing (negative) char is technically undefined behavior.

src/ccache/argprocessing.cpp
src/ccache/core/common.cpp
src/ccache/core/msvcshowincludesoutput.cpp
src/ccache/depfile.cpp
src/ccache/hashutil.cpp
src/ccache/util/environment.cpp
src/ccache/util/string.cpp
src/ccache/util/string.hpp

index 063bac342d044ae1baca0db5f5c64826faa89e50..76807a8ed52eb5682fe1a7efbe7ce0d031cf2b8b 100644 (file)
@@ -673,7 +673,7 @@ process_option_arg(const Context& ctx,
   }
 
   if (util::starts_with(arg, "-x")) {
-    if (arg.length() >= 3 && !islower(arg[2])) {
+    if (arg.length() >= 3 && !util::is_lower(arg[2])) {
       // -xCODE (where CODE can be e.g. Host or CORE-AVX2, always starting with
       // an uppercase letter) is an ordinary Intel compiler option, not a
       // language specification. (GCC's "-x" language argument is always
index 136aaa2f9e42558fbb883bf0706263d92030dc41..0a6ac2b8488904c7c1e66ed296bc561030f15af8 100644 (file)
@@ -227,7 +227,7 @@ get_diagnostics_path_length(std::string_view line)
     do {
       line.remove_suffix(1);
     } while (!line.empty() && line.back() != '('
-             && (std::isdigit(line.back()) || line.back() == ','));
+             && (util::is_digit(line.back()) || line.back() == ','));
 
     if (!line.empty() && line.back() == '(') {
       path_end = line.size() - 1;
index 8bc8798bf56c6aa3c6df85f6af6285ec6b68277f..66d5518f3ad12af8f16fe9b1ecbf0bc50e3c2c96 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2022-2024 Joel Rosdahl and other contributors
+// Copyright (C) 2022-2025 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -36,7 +36,7 @@ get_includes(std::string_view file_content, std::string_view prefix)
   for (std::string_view line : util::split_into_views(file_content, "\r\n")) {
     if (util::starts_with(line, prefix)) {
       size_t pos = prefix.size();
-      while (pos < line.size() && isspace(line[pos])) {
+      while (pos < line.size() && util::is_space(line[pos])) {
         ++pos;
       }
       std::string_view include = line.substr(pos);
index 03d710e712891d9ecf4825f5b7f6c01c6cbed0d9..9d823a847832f4b743ab6fed633d213a4229e581 100644 (file)
@@ -150,7 +150,7 @@ tokenize(std::string_view text)
 
   while (true) {
     // Find start of next token.
-    while (i < length && text[i] != '\n' && isspace(text[i])) {
+    while (i < length && text[i] != '\n' && util::is_space(text[i])) {
       ++i;
     }
 
@@ -182,7 +182,7 @@ tokenize(std::string_view text)
     // Parse token.
     std::string token;
     while (i < length) {
-      if (text[i] == ':' && token.length() == 1 && !isspace(token[0])
+      if (text[i] == ':' && token.length() == 1 && !util::is_space(token[0])
           && i + 1 < length && (text[i + 1] == '/' || text[i + 1] == '\\')) {
         // It's a Windows path, so the colon is not a separator and instead
         // added to the token.
@@ -191,7 +191,7 @@ tokenize(std::string_view text)
         continue;
       }
 
-      if (text[i] == ':' || isspace(text[i])
+      if (text[i] == ':' || util::is_space(text[i])
           || (text[i] == '\\' && i + 1 < length && text[i + 1] == '\n')) {
         // End of token.
         break;
index 38f7893d340bb0977bc4fefb1c9fa35c8cfaa169..bb76ed9f0c70836accd56035b406644b99a0344c 100644 (file)
@@ -72,9 +72,10 @@ check_for_temporal_macros_helper(std::string_view str, size_t pos)
 
   // Check char before and after macro to verify that the found macro isn't part
   // of another identifier.
-  if ((pos == 1 || (str[pos - 2] != '_' && !isalnum(str[pos - 2])))
+  if ((pos == 1 || (str[pos - 2] != '_' && !util::is_alnum(str[pos - 2])))
       && (pos + macro_len == str.length()
-          || (str[pos + macro_len] != '_' && !isalnum(str[pos + macro_len])))) {
+          || (str[pos + macro_len] != '_'
+              && !util::is_alnum(str[pos + macro_len])))) {
     return found;
   }
 
index b795001ab49d1d0ffb79886e07ba36f2216681b9..10ba23dcf6922ddfb0906798ad9147066d05b14b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2023-2024 Joel Rosdahl and other contributors
+// Copyright (C) 2023-2025 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -51,7 +51,7 @@ expand_environment_variables(const std::string& str)
         ++left;
       }
       right = left;
-      while (isalnum(*right) || *right == '_') {
+      while (util::is_alnum(*right) || *right == '_') {
         ++right;
       }
       if (curly && *right != '}') {
index b82d699571ecfdc8d71d53f968953ff784ce2931..950ba92babfd42548da2f0f56ddf4ded0a2abc66 100644 (file)
@@ -306,7 +306,7 @@ parse_size(const std::string& value)
     return tl::unexpected(FMT("invalid size: \"{}\"", value));
   }
 
-  while (isspace(*p)) {
+  while (util::is_space(*p)) {
     ++p;
   }
 
@@ -392,7 +392,7 @@ percent_decode(std::string_view string)
 {
   const auto from_hex = [](const char digit) {
     return static_cast<uint8_t>(
-      std::isdigit(digit) ? digit - '0' : std::tolower(digit) - 'a' + 10);
+      util::is_digit(digit) ? digit - '0' : util::to_lower(digit) - 'a' + 10);
   };
 
   std::string result;
@@ -401,8 +401,8 @@ percent_decode(std::string_view string)
   while (i < string.size()) {
     if (string[i] != '%') {
       result += string[i];
-    } else if (i + 2 >= string.size() || !std::isxdigit(string[i + 1])
-               || !std::isxdigit(string[i + 2])) {
+    } else if (i + 2 >= string.size() || !util::is_xdigit(string[i + 1])
+               || !util::is_xdigit(string[i + 2])) {
       return tl::unexpected(
         FMT("invalid percent-encoded string at position {}: {}", i, string));
     } else {
@@ -537,10 +537,10 @@ split_path_list(std::string_view path_list)
 std::string
 strip_whitespace(const std::string_view string)
 {
-  const auto is_space = [](const int ch) { return std::isspace(ch); };
-  const auto start = std::find_if_not(string.begin(), string.end(), is_space);
+  const auto start =
+    std::find_if_not(string.begin(), string.end(), util::is_space);
   const auto end =
-    std::find_if_not(string.rbegin(), string.rend(), is_space).base();
+    std::find_if_not(string.rbegin(), string.rend(), util::is_space).base();
   return start < end ? std::string(start, end) : std::string();
 }
 
@@ -549,7 +549,7 @@ to_lowercase(std::string_view string)
 {
   std::string result;
   result.resize(string.length());
-  std::transform(string.begin(), string.end(), result.begin(), tolower);
+  std::transform(string.begin(), string.end(), result.begin(), util::to_lower);
   return result;
 }
 
index 13d7fd40cddc7818dba0b4fd90bf949a751700a6..8b17efc05f0d6f830ffcba7814aa77926f20afcd 100644 (file)
@@ -88,6 +88,21 @@ std::string format_human_readable_size(uint64_t size,
 std::string format_iso8601_timestamp(const TimePoint& time,
                                      TimeZone time_zone = TimeZone::local);
 
+// Check if `ch` is alphanumeric.
+bool is_alnum(char ch);
+
+// Check if `ch` is a digit.
+bool is_digit(char ch);
+
+// Check if `ch` is a lowercase.
+bool is_lower(char ch);
+
+// Check if `ch` is a whitespace character.
+bool is_space(char ch);
+
+// Check if `ch` is a hexadecimal digit.
+bool is_xdigit(char ch);
+
 // Join stringified elements of `container` delimited by `delimiter` into a
 // string. There must exist an `std::string to_string(T::value_type)` function.
 template<typename T>
@@ -208,6 +223,9 @@ bool starts_with(std::string_view string, std::string_view prefix);
 // Strip whitespace from left and right side of a string.
 [[nodiscard]] std::string strip_whitespace(std::string_view string);
 
+// Return lowercase `ch`.
+char to_lower(char ch);
+
 // Convert a string to lowercase.
 [[nodiscard]] std::string to_lowercase(std::string_view string);
 
@@ -220,6 +238,36 @@ ends_with(const std::string_view string, const std::string_view suffix)
          && string.substr(string.length() - suffix.length()) == suffix;
 }
 
+inline bool
+is_alnum(char ch)
+{
+  return std::isalnum(static_cast<unsigned char>(ch));
+}
+
+inline bool
+is_digit(char ch)
+{
+  return std::isdigit(static_cast<unsigned char>(ch));
+}
+
+inline bool
+is_lower(char ch)
+{
+  return std::islower(static_cast<unsigned char>(ch));
+}
+
+inline bool
+is_space(char ch)
+{
+  return std::isspace(static_cast<unsigned char>(ch));
+}
+
+inline bool
+is_xdigit(char ch)
+{
+  return std::isxdigit(static_cast<unsigned char>(ch));
+}
+
 template<typename T>
 inline std::string
 join(const T& container, const std::string_view delimiter)
@@ -255,4 +303,10 @@ starts_with(const std::string_view string, const std::string_view prefix)
   return string.substr(0, prefix.size()) == prefix;
 }
 
+inline char
+to_lower(char ch)
+{
+  return std::tolower(static_cast<unsigned char>(ch));
+}
+
 } // namespace util