From: Joel Rosdahl Date: Sun, 19 Jul 2026 15:15:30 +0000 (+0200) Subject: feat: Detect C23 #embed directive and improve .incbin handling (#1765) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=thirdparty%2Fccache.git feat: Detect C23 #embed directive and improve .incbin handling (#1765) Ccache's direct mode code scanner now detects usage of the C23 #embed directive. This avoids false-positive cache hits in direct (and depend) mode. Ccache now also detects .incbin directives in depend mode, thus avoiding false positives in depend mode. (They were detected in non-depend mode before.) The new scanner detects #embed and .incbin as well as "temporal macros" (__DATE__, __TIME__, etc) in one go. Similar to the old temporal macro scanner, there is a faster AVX2-enabled version and a slower non-SIMD fallback version. Under the assumption that most CPUs have AVX2 support these days, the old Boyer-Moore-Horspool algorithm for the scalar case has been removed. Some unit tests have been contributed by Haaris Rahman. Fixes #1540. Co-authored-by: Haaris Rahman --- diff --git a/src/ccache/ccache.cpp b/src/ccache/ccache.cpp index 2111b874..0988e406 100644 --- a/src/ccache/ccache.cpp +++ b/src/ccache/ccache.cpp @@ -458,23 +458,22 @@ remember_include_file(Context& ctx, } } - if (!hash_binary_file(ctx, file_digest, path2)) { + auto ret = hash_binary_file(ctx, path2); + if (!ret) { return tl::unexpected(Statistic::bad_input_file); } + file_digest = *ret; cpp_hash.hash_delimiter(using_pch_sum ? "pch_sum_hash" : "pch_hash"); cpp_hash.hash(util::format_legacy_digest(file_digest)); } if (ctx.config.direct_mode()) { if (!is_pch) { // else: the file has already been hashed. - auto ret = hash_source_code_file(ctx, file_digest, path2); - if (ret.contains(HashSourceCode::error)) { + auto ret = hash_source_code_file(ctx, path2); + if (!ret) { return tl::unexpected(Statistic::bad_input_file); } - if (ret.contains(HashSourceCode::found_time)) { - LOG("Disabling direct mode"); - ctx.config.set_direct_mode(false); - } + file_digest = *ret; } if (depend_mode_hash) { @@ -2363,18 +2362,15 @@ get_manifest_key(Context& ctx, Hash& hash) } hash.hash_delimiter("sourcecode hash"); - Hash::Digest input_file_digest; - auto ret = - hash_source_code_file(ctx, input_file_digest, ctx.args_info.input_file); - if (ret.contains(HashSourceCode::error)) { + auto input_file_digest = hash_source_code_file(ctx, ctx.args_info.input_file); + if (!input_file_digest) { return tl::unexpected(Statistic::internal_error); } - if (ret.contains(HashSourceCode::found_time)) { - LOG("Disabling direct mode"); - ctx.config.set_direct_mode(false); + if (!ctx.config.direct_mode()) { + // Direct mode was disabled by hash_source_code_file. return {}; } - hash.hash(util::format_legacy_digest(input_file_digest)); + hash.hash(util::format_legacy_digest(*input_file_digest)); return hash.digest(); } diff --git a/src/ccache/core/manifest.cpp b/src/ccache/core/manifest.cpp index 057b2385..56e29ea9 100644 --- a/src/ccache/core/manifest.cpp +++ b/src/ccache/core/manifest.cpp @@ -168,7 +168,7 @@ Manifest::read(std::span data) } std::optional -Manifest::look_up_result_digest(const Context& ctx) const +Manifest::look_up_result_digest(Context& ctx) const { std::unordered_map stated_files; std::unordered_map hashed_files; @@ -371,7 +371,7 @@ Manifest::get_file_info_index( bool Manifest::result_matches( - const Context& ctx, + Context& ctx, const ResultEntry& result, std::unordered_map& stated_files, std::unordered_map& hashed_files) const @@ -433,18 +433,17 @@ Manifest::result_matches( auto hashed_files_iter = hashed_files.find(path); if (hashed_files_iter == hashed_files.end()) { - Hash::Digest actual_digest; - auto ret = hash_source_code_file(ctx, actual_digest, path, fs.size); - if (ret.contains(HashSourceCode::error)) { + auto actual_digest = hash_source_code_file(ctx, path, fs.size); + if (!actual_digest) { LOG("Failed hashing {}", path); return false; } - if (ret.contains(HashSourceCode::found_time)) { - // hash_source_code_file has already logged. + if (!ctx.config.direct_mode()) { + // Direct mode was disabled by hash_source_code_file. return false; } - hashed_files_iter = hashed_files.emplace(path, actual_digest).first; + hashed_files_iter = hashed_files.emplace(path, *actual_digest).first; } if (hashed_files_iter->second != fi.digest) { diff --git a/src/ccache/core/manifest.hpp b/src/ccache/core/manifest.hpp index 76e1d56b..4dbe3f43 100644 --- a/src/ccache/core/manifest.hpp +++ b/src/ccache/core/manifest.hpp @@ -54,7 +54,7 @@ public: void read(std::span data); - std::optional look_up_result_digest(const Context& ctx) const; + std::optional look_up_result_digest(Context& ctx) const; bool add_result( const Hash::Digest& result_key, @@ -103,7 +103,7 @@ private: const FileStater& file_state); bool result_matches( - const Context& ctx, + Context& ctx, const ResultEntry& result, std::unordered_map& stated_files, std::unordered_map& hashed_files) const; diff --git a/src/ccache/hashutil.cpp b/src/ccache/hashutil.cpp index 16032944..411e51d9 100644 --- a/src/ccache/hashutil.cpp +++ b/src/ccache/hashutil.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -44,30 +43,68 @@ # include #endif +#include +#include + namespace fs = util::filesystem; namespace { +const char* +skip_horizontal_whitespace(const char* p, const char* end) +{ + while (p < end && (*p == ' ' || *p == '\t')) { + ++p; + } + return p; +} + +const char* +skip_line_continuation(const char* p, const char* end) +{ + if (p < end && end - p >= 2 && *p == '\\' && (p[1] == '\n' || p[1] == '\r')) { + p += 2; + if (p < end && p[-1] == '\r' && *p == '\n') { + ++p; + } + } + return p; +} + +const char* +skip_whitespace_and_continuations(const char* p, const char* end) +{ + while (p < end) { + const char* previous = p; + p = skip_horizontal_whitespace(p, end); + p = skip_line_continuation(p, end); + if (p == previous) { + break; + } + } + return p; +} + // Pre-condition: str[pos - 1] == '_' -HashSourceCode -check_for_temporal_macros_helper(std::string_view str, size_t pos) +SourceCodeScan +check_for_temporal_macros(std::string_view str, size_t pos) { if (pos + 7 > str.length()) { - return HashSourceCode::ok; + return SourceCodeScan::none; } - HashSourceCode found = HashSourceCode::ok; + SourceCodeScan found = SourceCodeScan::none; int macro_len = 7; if (memcmp(&str[pos], "_DATE__", 7) == 0) { - found = HashSourceCode::found_date; + found = SourceCodeScan::found_date; } else if (memcmp(&str[pos], "_TIME__", 7) == 0) { - found = HashSourceCode::found_time; + found = SourceCodeScan::found_time; } else if (pos + 12 <= str.length() && memcmp(&str[pos], "_TIMESTAMP__", 12) == 0) { - found = HashSourceCode::found_timestamp; + found = SourceCodeScan::found_timestamp; macro_len = 12; } else { - return HashSourceCode::ok; + return SourceCodeScan::none; } // Check char before and after macro to verify that the found macro isn't part @@ -79,116 +116,107 @@ check_for_temporal_macros_helper(std::string_view str, size_t pos) return found; } - return HashSourceCode::ok; + return SourceCodeScan::none; } -HashSourceCodeResult -check_for_temporal_macros_bmh(std::string_view str, size_t start = 0) +SourceCodeScan +check_for_embed_directive(std::string_view str, size_t pos) { - HashSourceCodeResult result; - - // We're using the Boyer-Moore-Horspool algorithm, which searches starting - // from the *end* of the needle. Our needles are 8 characters long, so i - // starts at 7. - size_t i = start + 7; - - while (i < str.length()) { - // Check whether the substring ending at str[i] has the form "_....E..". On - // the assumption that 'E' is less common in source than '_', we check - // str[i-2] first. - if (str[i - 2] == 'E' && str[i - 7] == '_') { - result.insert(check_for_temporal_macros_helper(str, i - 6)); - } + static constexpr char embed_data[] = {'e', 'm', 'b', 'e', 'd'}; + static constexpr size_t embed_size = std::size(embed_data); - // macro_skip tells us how far we can skip forward upon seeing str[i] at - // the end of a substring. - i += macro_skip[(uint8_t)str[i]]; + if (str[pos] != '#') { + return SourceCodeScan::none; } - return result; -} + const char* const begin = str.data(); + const char* const end = begin + str.size(); + const char* p = begin + pos + 1; + p = skip_whitespace_and_continuations(p, end); -#ifdef HAVE_AVX2 -// The following algorithm, which uses AVX2 instructions to find __DATE__, -// __TIME__ and __TIMESTAMP__, is heavily inspired by -// . -# ifndef _MSC_VER -__attribute__((target("avx2"))) -# endif -HashSourceCodeResult -check_for_temporal_macros_avx2(std::string_view str) -{ - HashSourceCodeResult result; + if (static_cast(end - p) < embed_size + || std::memcmp(p, embed_data, embed_size) != 0) { + return SourceCodeScan::none; + } - // Set all 32 bytes in first and last to '_' and 'E' respectively. - const __m256i first = _mm256_set1_epi8('_'); - const __m256i last = _mm256_set1_epi8('E'); + const char* after_embed = p + embed_size; + if (after_embed < end + && (std::isalnum(static_cast(*after_embed)) + || *after_embed == '_')) { + return SourceCodeScan::none; + } - size_t pos = 0; - for (; pos + 5 + 32 <= str.length(); pos += 32) { - // Load 32 bytes from the current position in the input string, with - // block_last being offset 5 bytes (i.e. the offset of 'E' in all three - // macros). - const __m256i block_first = - _mm256_loadu_si256(reinterpret_cast(&str[pos])); - const __m256i block_last = - _mm256_loadu_si256(reinterpret_cast(&str[pos + 5])); + // The resource identifier may be a macro that expands to a quoted or angle + // bracket delimited filename, so it cannot be reliably parsed here. + return SourceCodeScan::found_embed; +} - // For i in 0..31: - // eq_X[i] = 0xFF if X[i] == block_X[i] else 0 - const __m256i eq_first = _mm256_cmpeq_epi8(first, block_first); - const __m256i eq_last = _mm256_cmpeq_epi8(last, block_last); +SourceCodeScan +check_for_incbin_directive(std::string_view str, size_t pos) +{ + static constexpr char incbin_data[] = {'.', 'i', 'n', 'c', 'b', 'i', 'n'}; + static constexpr size_t incbin_size = std::size(incbin_data); - // Set bit i in mask if byte i in both eq_first and eq_last has the most - // significant bit set. - uint32_t mask = _mm256_movemask_epi8(_mm256_and_si256(eq_first, eq_last)); + if (pos + incbin_size > str.size() + || std::memcmp(&str[pos], incbin_data, incbin_size) != 0) { + return SourceCodeScan::none; + } - // A bit set in mask now indicates a possible location for a temporal macro. - while (mask != 0) { - // The start position + 1 (as we know the first char is _). -# ifndef _MSC_VER - const auto start = pos + __builtin_ctz(mask) + 1; -# else - unsigned long index; - _BitScanForward(&index, mask); - const auto start = pos + index + 1; -# endif + const char* const begin = str.data(); + const char* const end = begin + str.size(); + const char* p = begin + pos + incbin_size; + p = skip_horizontal_whitespace(p, end); - // Clear the least significant bit set. - mask = mask & (mask - 1); + return p < end && (*p == '"' || (*p == '\\' && p + 1 < end && p[1] == '"')) + ? SourceCodeScan::found_incbin + : SourceCodeScan::none; +} - result.insert(check_for_temporal_macros_helper(str, start)); +void +check_for_source_code_patterns_scalar(std::string_view str, + size_t start, + SourceCodeScanResult& result) +{ + for (size_t i = start; i < str.size(); ++i) { + if (str[i] == '_') { + result.insert(check_for_temporal_macros(str, i + 1)); + } + if (!result.contains(SourceCodeScan::found_embed) && str[i] == '#') { + result.insert(check_for_embed_directive(str, i)); + } + if (!result.contains(SourceCodeScan::found_incbin) && str[i] == '.') { + result.insert(check_for_incbin_directive(str, i)); } } - - result.insert(check_for_temporal_macros_bmh(str, pos)); - - return result; } -#endif -HashSourceCodeResult +SourceCodeScanResult check_for_source_code_patterns(std::string_view str) { #ifdef HAVE_AVX2 if (util::cpu_supports_avx2()) { - return check_for_temporal_macros_avx2(str); + return check_for_source_code_patterns_avx2(str); } #endif - return check_for_temporal_macros_bmh(str); + + SourceCodeScanResult result; + check_for_source_code_patterns_scalar(str, 0, result); + return result; } -HashSourceCodeResult +std::optional do_hash_file(const Context& ctx, Hash::Digest& digest, const fs::path& path, size_t size_hint, - bool check_temporal_macros) + bool scan_source) { #ifdef INODE_CACHE_SUPPORTED - const InodeCache::ContentType content_type = - check_temporal_macros ? InodeCache::ContentType::checked_for_temporal_macros - : InodeCache::ContentType::raw; + InodeCache::ContentType content_type = + scan_source + ? InodeCache::ContentType::checked_for_temporal_macros_and_directives + : InodeCache::ContentType::raw; + if (ctx.config.inode_cache()) { const auto result = ctx.inode_cache.get(path, content_type); if (result) { @@ -203,18 +231,18 @@ do_hash_file(const Context& ctx, const auto data = util::read_file(path, size_hint); if (!data) { LOG("Failed to read {}: {}", path, data.error()); - return HashSourceCodeResult(HashSourceCode::error); - } - - HashSourceCodeResult result; - if (check_temporal_macros) { - result.insert(check_for_source_code_patterns(util::to_string_view(*data))); + return std::nullopt; } + auto str = util::to_string_view(*data); Hash hash; - hash.hash(*data); + hash.hash(str); digest = hash.digest(); + SourceCodeScanResult result; + if (scan_source) { + result = check_for_source_code_patterns(str); + } #ifdef INODE_CACHE_SUPPORTED ctx.inode_cache.put(path, content_type, digest, result); #endif @@ -225,89 +253,210 @@ do_hash_file(const Context& ctx, } // namespace #ifdef HAVE_AVX2 -HashSourceCodeResult +// The following is heavily inspired by +// . +# ifndef _MSC_VER +__attribute__((target("avx2"))) +# endif +SourceCodeScanResult check_for_source_code_patterns_avx2(std::string_view str) { - return check_for_temporal_macros_avx2(str); + SourceCodeScanResult result; + + const __m256i underscore = _mm256_set1_epi8('_'); + const __m256i temporal_last = _mm256_set1_epi8('E'); + const __m256i hash_sign = _mm256_set1_epi8('#'); + const __m256i period = _mm256_set1_epi8('.'); + const __m256i incbin_last = _mm256_set1_epi8('n'); + + size_t pos = 0; + for (; pos + 32 <= str.length(); pos += 32) { + const __m256i block = + _mm256_loadu_si256(reinterpret_cast(&str[pos])); + + uint32_t temporal_mask = 0; + if (pos + 5 + 32 <= str.length()) { + const __m256i block_last = + _mm256_loadu_si256(reinterpret_cast(&str[pos + 5])); + temporal_mask = _mm256_movemask_epi8( + _mm256_and_si256(_mm256_cmpeq_epi8(underscore, block), + _mm256_cmpeq_epi8(temporal_last, block_last))); + } + + uint32_t embed_mask = 0; + if (!result.contains(SourceCodeScan::found_embed)) { + embed_mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(hash_sign, block)); + } + + uint32_t incbin_mask = 0; + if (!result.contains(SourceCodeScan::found_incbin) + && pos + 6 + 32 <= str.length()) { + const __m256i block_last = + _mm256_loadu_si256(reinterpret_cast(&str[pos + 6])); + incbin_mask = _mm256_movemask_epi8( + _mm256_and_si256(_mm256_cmpeq_epi8(period, block), + _mm256_cmpeq_epi8(incbin_last, block_last))); + } + + while (temporal_mask != 0) { +# ifndef _MSC_VER + const auto start = pos + __builtin_ctz(temporal_mask) + 1; +# else + unsigned long index; + _BitScanForward(&index, temporal_mask); + const auto start = pos + index + 1; +# endif + temporal_mask &= temporal_mask - 1; + result.insert(check_for_temporal_macros(str, start)); + } + + while (embed_mask != 0) { +# ifndef _MSC_VER + const auto start = pos + __builtin_ctz(embed_mask); +# else + unsigned long index; + _BitScanForward(&index, embed_mask); + const auto start = pos + index; +# endif + embed_mask &= embed_mask - 1; + result.insert(check_for_embed_directive(str, start)); + } + + while (incbin_mask != 0) { +# ifndef _MSC_VER + const auto start = pos + __builtin_ctz(incbin_mask); +# else + unsigned long index; + _BitScanForward(&index, incbin_mask); + const auto start = pos + index; +# endif + incbin_mask &= incbin_mask - 1; + result.insert(check_for_incbin_directive(str, start)); + } + } + + const size_t fallback_start = pos >= 32 ? pos - 32 : 0; + check_for_source_code_patterns_scalar(str, fallback_start, result); + return result; } #endif -HashSourceCodeResult +SourceCodeScanResult check_for_source_code_patterns_scalar(std::string_view str) { - return check_for_temporal_macros_bmh(str); + SourceCodeScanResult result; + check_for_source_code_patterns_scalar(str, 0, result); + return result; } -HashSourceCodeResult -hash_source_code_file(const Context& ctx, - Hash::Digest& digest, - const fs::path& path, - size_t size_hint) +std::optional +hash_source_code_file(Context& ctx, const fs::path& path, size_t size_hint) { - const bool check_temporal_macros = - !ctx.config.sloppiness().contains(core::Sloppy::time_macros); - auto result = - do_hash_file(ctx, digest, path, size_hint, check_temporal_macros); + Hash::Digest digest; + auto opt_result = do_hash_file(ctx, digest, path, size_hint, true); + if (!opt_result) { + return std::nullopt; + } + auto& result = *opt_result; - if (!check_temporal_macros || result.empty() - || result.contains(HashSourceCode::error)) { - return result; + if (result.contains(SourceCodeScan::found_embed)) { + LOG("Found #em{}bed in {}", "", path); + } + if (result.contains(SourceCodeScan::found_incbin)) { + std::string_view suffix; + if (ctx.config.sloppiness().contains(core::Sloppy::incbin)) { + result.erase(SourceCodeScan::found_incbin); + suffix = " (ignored)"; + } + LOG("Found .inc{}bin in {}", "", path); + } + if (result.contains(SourceCodeScan::found_time)) { + std::string_view suffix; + if (ctx.config.sloppiness().contains(core::Sloppy::time_macros)) { + result.erase(SourceCodeScan::found_time); + suffix = " (ignored)"; + } + LOG("Found __TI{}ME__ in {}{}", "", path, suffix); + } + if (result.contains(SourceCodeScan::found_date)) { + std::string_view suffix; + if (ctx.config.sloppiness().contains(core::Sloppy::time_macros)) { + result.erase(SourceCodeScan::found_date); + suffix = " (ignored)"; + } + LOG("Found __DA{}TE__ in {}{}", "", path, suffix); + } + if (result.contains(SourceCodeScan::found_timestamp)) { + std::string_view suffix; + if (ctx.config.sloppiness().contains(core::Sloppy::time_macros)) { + result.erase(SourceCodeScan::found_timestamp); + suffix = " (ignored)"; + } + LOG("Found __TIME{}STAMP__ in {}{}", "", path, suffix); + } + + if (result.contains(SourceCodeScan::found_time) + || result.contains(SourceCodeScan::found_embed) + || result.contains(SourceCodeScan::found_incbin)) { + LOG("Disabling direct mode"); + ctx.config.set_direct_mode(false); + return digest; } - if (result.contains(HashSourceCode::found_time)) { - // We don't know for sure that the program actually uses the __TIME__ macro, - // but we have to assume it anyway and hash the time stamp. However, that's - // not very useful since the chance that we get a cache hit later the same + const bool contains_temporal_macro = + result.contains(SourceCodeScan::found_time) + || result.contains(SourceCodeScan::found_date) + || result.contains(SourceCodeScan::found_timestamp); + if (!contains_temporal_macro) { + return digest; + } + + if (result.contains(SourceCodeScan::found_time)) { + // We don't know for sure that the program actually uses the time macro, but + // we have to assume it anyway and hash the time stamp. However, that's not + // very useful since the chance that we get a cache hit later the same // second should be quite slim... So, just signal back to the caller that - // __TIME__ has been found so that the direct mode can be disabled. - LOG("Found __TIME__ in {}", path); - return result; + // the macro has been found so that the direct mode can be disabled. + return digest; } - // __DATE__ or __TIMESTAMP__ found. We now make sure that the digest changes - // if the (potential) expansion of those macros changes by computing a new - // digest comprising the file digest and time information that represents the - // macro expansions. + // Date or timestamp found. We now make sure that the digest changes if the + // (potential) expansion of those macros changes by computing a new digest + // comprising the file digest and time information that represents the macro + // expansions. Hash hash; hash.hash(util::format_legacy_digest(digest)); - if (result.contains(HashSourceCode::found_date)) { - LOG("Found __DATE__ in {}", path); - + if (result.contains(SourceCodeScan::found_date)) { hash.hash_delimiter("date"); auto now = util::localtime(); if (!now) { - result.insert(HashSourceCode::error); - return result; + return std::nullopt; } hash.hash(now->tm_year); hash.hash(now->tm_mon); hash.hash(now->tm_mday); - // If the compiler has support for it, the expansion of __DATE__ will change - // according to the value of SOURCE_DATE_EPOCH. Note: We have to hash both - // SOURCE_DATE_EPOCH and the current date since we can't be sure that the - // compiler honors SOURCE_DATE_EPOCH. + // If the compiler has support for it, the expansion of the date macro will + // change according to the value of SOURCE_DATE_EPOCH. Note: We have to hash + // both SOURCE_DATE_EPOCH and the current date since we can't be sure that + // the compiler honors SOURCE_DATE_EPOCH. const auto source_date_epoch = getenv("SOURCE_DATE_EPOCH"); if (source_date_epoch) { hash.hash(source_date_epoch); } } - if (result.contains(HashSourceCode::found_timestamp)) { - LOG("Found __TIMESTAMP__ in {}", path); - + if (result.contains(SourceCodeScan::found_timestamp)) { util::DirEntry dir_entry(path); if (!dir_entry.is_regular_file()) { - result.insert(HashSourceCode::error); - return result; + return std::nullopt; } auto modified_time = util::localtime(dir_entry.mtime()); if (!modified_time) { - result.insert(HashSourceCode::error); - return result; + return std::nullopt; } hash.hash_delimiter("timestamp"); char timestamp[26]; @@ -315,28 +464,27 @@ hash_source_code_file(const Context& ctx, hash.hash(timestamp); } - digest = hash.digest(); - return result; + return hash.digest(); } -bool -hash_binary_file(const Context& ctx, - Hash::Digest& digest, - const fs::path& path, - size_t size_hint) +std::optional +hash_binary_file(const Context& ctx, const fs::path& path, size_t size_hint) { - return do_hash_file(ctx, digest, path, size_hint, false).empty(); + Hash::Digest digest; + if (!do_hash_file(ctx, digest, path, size_hint, false)) { + return std::nullopt; + } + return digest; } bool hash_binary_file(const Context& ctx, Hash& hash, const fs::path& path) { - Hash::Digest digest; - const bool success = hash_binary_file(ctx, digest, path); - if (success) { - hash.hash(util::format_legacy_digest(digest)); + const auto result = hash_binary_file(ctx, path); + if (result) { + hash.hash(util::format_legacy_digest(*result)); } - return success; + return result.has_value(); } bool diff --git a/src/ccache/hashutil.hpp b/src/ccache/hashutil.hpp index a1030205..b10ae2b2 100644 --- a/src/ccache/hashutil.hpp +++ b/src/ccache/hashutil.hpp @@ -23,45 +23,46 @@ #include #include +#include #include #include class Config; class Context; -enum class HashSourceCode { - ok = 0, - error = 1U << 0, - found_date = 1U << 1, - found_time = 1U << 2, - found_timestamp = 1U << 3, +// The result of scanning source code. +// +// Note: Probably need to bump Inode cache version if changing values or +// semantics of this enum. +enum class SourceCodeScan { + none = 0, + found_date = 1U << 0, + found_time = 1U << 1, + found_timestamp = 1U << 2, + found_embed = 1U << 3, + found_incbin = 1U << 4, }; -using HashSourceCodeResult = util::BitSet; +using SourceCodeScanResult = util::BitSet; #ifdef HAVE_AVX2 // Search for source code patterns in `str`, AVX2 version. -HashSourceCodeResult check_for_source_code_patterns_avx2(std::string_view str); +SourceCodeScanResult check_for_source_code_patterns_avx2(std::string_view str); #endif // Search for source code patterns in `str`, non-SIMD version. -HashSourceCodeResult +SourceCodeScanResult check_for_source_code_patterns_scalar(std::string_view str); // Hash a source code file using the inode cache if enabled. -HashSourceCodeResult hash_source_code_file(const Context& ctx, - Hash::Digest& digest, - const std::filesystem::path& path, - size_t size_hint = 0); +std::optional hash_source_code_file( + Context& ctx, const std::filesystem::path& path, size_t size_hint = 0); // Hash a binary file (using the inode cache if enabled) and put its digest in // `digest` -// -// Returns true on success, otherwise false. -bool hash_binary_file(const Context& ctx, - Hash::Digest& digest, - const std::filesystem::path& path, - size_t size_hint = 0); +std::optional hash_binary_file(const Context& ctx, + const std::filesystem::path& path, + size_t size_hint = 0); // Hash a binary file (using the inode cache if enabled) and hash the digest to // `hash`. diff --git a/src/ccache/inodecache.cpp b/src/ccache/inodecache.cpp index 22aef2d1..021bf0de 100644 --- a/src/ccache/inodecache.cpp +++ b/src/ccache/inodecache.cpp @@ -76,7 +76,7 @@ namespace { // Note: The key is hashed using the main hash algorithm, so the version number // does not need to be incremented if said algorithm is changed (except if the // digest size changes since that affects the entry format). -const uint32_t k_version = 2; +const uint32_t k_version = 3; // Note: Increment the version number if constants affecting storage size are // changed. @@ -105,6 +105,11 @@ static_assert( static_assert( static_cast(InodeCache::ContentType::checked_for_temporal_macros) == 1, "Numeric value is part of key, increment version number if changed."); +static_assert( + static_cast( + InodeCache::ContentType::checked_for_temporal_macros_and_directives) + == 2, + "Numeric value is part of key, increment version number if changed."); bool fd_is_on_known_to_work_file_system(int fd) @@ -533,7 +538,7 @@ InodeCache::available(int fd) return fd_is_on_known_to_work_file_system(fd); } -std::optional> +std::optional> InodeCache::get(const fs::path& path, ContentType type) { if (!initialize()) { @@ -545,7 +550,7 @@ InodeCache::get(const fs::path& path, ContentType type) return std::nullopt; } - std::optional result; + std::optional result; Hash::Digest file_digest; const bool success = with_bucket(key_digest, [&](const auto bucket) { for (uint32_t i = 0; i < k_num_entries; ++i) { @@ -558,7 +563,7 @@ InodeCache::get(const fs::path& path, ContentType type) file_digest = bucket->entries[0].file_digest; result = - HashSourceCodeResult::from_bitmask(bucket->entries[0].return_value); + SourceCodeScanResult::from_bitmask(bucket->entries[0].return_value); break; } } @@ -586,7 +591,7 @@ bool InodeCache::put(const fs::path& path, ContentType type, const Hash::Digest& file_digest, - HashSourceCodeResult return_value) + SourceCodeScanResult return_value) { if (!initialize()) { return false; diff --git a/src/ccache/inodecache.hpp b/src/ccache/inodecache.hpp index 21e3431a..9740760d 100644 --- a/src/ccache/inodecache.hpp +++ b/src/ccache/inodecache.hpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2025 Joel Rosdahl and other contributors +// Copyright (C) 2020-2026 Joel Rosdahl and other contributors // // See doc/authors.adoc for a complete list of contributors. // @@ -45,9 +45,11 @@ public: enum class ContentType { // The file was not scanned for temporal macros. raw = 0, - // The file was checked for temporal macros (see check_for_temporal_macros - // in hashutil). + // Legacy value: The file was checked for temporal macros. checked_for_temporal_macros = 1, + // The file was checked for temporal macros as well as embed and incbin + // directives. + checked_for_temporal_macros_and_directives = 2, }; // `min_age` specifies how old a file must be to be put in the cache. The @@ -79,7 +81,7 @@ public: // Get saved hash digest and return value from a previous call to // do_hash_file() in hashutil.cpp. - std::optional> + std::optional> get(const std::filesystem::path& path, ContentType type); // Put hash digest and return value from a successful call to do_hash_file() @@ -89,7 +91,7 @@ public: bool put(const std::filesystem::path& path, ContentType type, const Hash::Digest& file_digest, - HashSourceCodeResult return_value); + SourceCodeScanResult return_value); // Unmaps the current cache and removes the mapped file from disk. // diff --git a/src/ccache/macroskip.hpp b/src/ccache/macroskip.hpp deleted file mode 100644 index 92cb3e2f..00000000 --- a/src/ccache/macroskip.hpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2010-2021 Joel Rosdahl and other contributors -// -// See doc/authors.adoc for a complete list of contributors. -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program; if not, write to the Free Software Foundation, Inc., 51 -// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -#pragma once - -#include - -// A Boyer-Moore-Horspool skip table used for searching for the strings -// "__TIME__", "__DATE__" and "__TIMESTAMP__". -// -// macro_skip[c] = 8 for all c not in "__TIME__", "__DATE__" and "__TIMEST". -// -// The other characters map as follows: -// -// _ -> 1 -// A -> 4 -// D -> 5 -// E -> 2 -// I -> 4 -// M -> 3 -// T -> 3 -// S -> 1 -// -// -// This was generated with the following Python script: -// -// m = {'_': 1, -// 'A': 4, -// 'D': 5, -// 'E': 2, -// 'I': 4, -// 'M': 3, -// 'S': 1, -// 'T': 3} -// -// for i in range(0, 256): -// if chr(i) in m: -// num = m[chr(i)] -// else: -// num = 8 -// print ("%d, " % num), -// -// if i % 16 == 15: -// print "" - -const uint32_t macro_skip[] = { - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 8, 8, 5, 2, 8, 8, 8, 4, 8, 8, 8, 3, - 8, 8, 8, 8, 8, 1, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, -}; diff --git a/test/suites/inode_cache.bash b/test/suites/inode_cache.bash index 51a809f8..f85e4f89 100644 --- a/test/suites/inode_cache.bash +++ b/test/suites/inode_cache.bash @@ -9,7 +9,7 @@ SUITE_inode_cache_PROBE() { touch test.c $CCACHE $COMPILER -c test.c - if [[ ! -f "${CCACHE_TEMPDIR}/inode-cache-32.v2" && ! -f "${CCACHE_TEMPDIR}/inode-cache-64.v2" ]]; then + if [[ ! -f "${CCACHE_TEMPDIR}/inode-cache-32.v3" && ! -f "${CCACHE_TEMPDIR}/inode-cache-64.v3" ]]; then local fs_type=$(stat -fLc %T "${CCACHE_DIR}") echo "inode cache not supported on ${fs_type}" fi diff --git a/unittest/test_hashutil.cpp b/unittest/test_hashutil.cpp index 32f28947..1c6fa8e2 100644 --- a/unittest/test_hashutil.cpp +++ b/unittest/test_hashutil.cpp @@ -18,6 +18,7 @@ #include "testutil.hpp" +#include #include #include #include @@ -43,7 +44,7 @@ hco(Hash& hash, const std::string& command, const std::string& compiler) #endif } -using SourceCodePatternChecker = HashSourceCodeResult (*)(std::string_view); +using SourceCodePatternChecker = SourceCodeScanResult (*)(std::string_view); static void check_temporal_macros(SourceCodePatternChecker check) @@ -103,62 +104,62 @@ check_temporal_macros(SourceCodePatternChecker check) "#define alphabet abcdefghijklmnopqrstuvwxyz\n" "a__DATE__"; - CHECK(check(time_start).contains(HashSourceCode::found_time)); + CHECK(check(time_start).contains(SourceCodeScan::found_time)); CHECK(check(time_start.substr(1)).empty()); - CHECK(check(time_middle.substr(0)).contains(HashSourceCode::found_time)); - CHECK(check(time_middle.substr(1)).contains(HashSourceCode::found_time)); - CHECK(check(time_middle.substr(2)).contains(HashSourceCode::found_time)); - CHECK(check(time_middle.substr(3)).contains(HashSourceCode::found_time)); - CHECK(check(time_middle.substr(4)).contains(HashSourceCode::found_time)); - CHECK(check(time_middle.substr(5)).contains(HashSourceCode::found_time)); - CHECK(check(time_middle.substr(6)).contains(HashSourceCode::found_time)); - CHECK(check(time_middle.substr(7)).contains(HashSourceCode::found_time)); + CHECK(check(time_middle.substr(0)).contains(SourceCodeScan::found_time)); + CHECK(check(time_middle.substr(1)).contains(SourceCodeScan::found_time)); + CHECK(check(time_middle.substr(2)).contains(SourceCodeScan::found_time)); + CHECK(check(time_middle.substr(3)).contains(SourceCodeScan::found_time)); + CHECK(check(time_middle.substr(4)).contains(SourceCodeScan::found_time)); + CHECK(check(time_middle.substr(5)).contains(SourceCodeScan::found_time)); + CHECK(check(time_middle.substr(6)).contains(SourceCodeScan::found_time)); + CHECK(check(time_middle.substr(7)).contains(SourceCodeScan::found_time)); - CHECK(check(time_end).contains(HashSourceCode::found_time)); + CHECK(check(time_end).contains(SourceCodeScan::found_time)); CHECK(check(time_end.substr(time_end.length() - 8)) - .contains(HashSourceCode::found_time)); + .contains(SourceCodeScan::found_time)); CHECK(check(time_end.substr(time_end.length() - 7)).empty()); - CHECK(check(date_start).contains(HashSourceCode::found_date)); + CHECK(check(date_start).contains(SourceCodeScan::found_date)); CHECK(check(date_start.substr(1)).empty()); - CHECK(check(date_middle.substr(0)).contains(HashSourceCode::found_date)); - CHECK(check(date_middle.substr(1)).contains(HashSourceCode::found_date)); - CHECK(check(date_middle.substr(2)).contains(HashSourceCode::found_date)); - CHECK(check(date_middle.substr(3)).contains(HashSourceCode::found_date)); - CHECK(check(date_middle.substr(4)).contains(HashSourceCode::found_date)); - CHECK(check(date_middle.substr(5)).contains(HashSourceCode::found_date)); - CHECK(check(date_middle.substr(6)).contains(HashSourceCode::found_date)); - CHECK(check(date_middle.substr(7)).contains(HashSourceCode::found_date)); + CHECK(check(date_middle.substr(0)).contains(SourceCodeScan::found_date)); + CHECK(check(date_middle.substr(1)).contains(SourceCodeScan::found_date)); + CHECK(check(date_middle.substr(2)).contains(SourceCodeScan::found_date)); + CHECK(check(date_middle.substr(3)).contains(SourceCodeScan::found_date)); + CHECK(check(date_middle.substr(4)).contains(SourceCodeScan::found_date)); + CHECK(check(date_middle.substr(5)).contains(SourceCodeScan::found_date)); + CHECK(check(date_middle.substr(6)).contains(SourceCodeScan::found_date)); + CHECK(check(date_middle.substr(7)).contains(SourceCodeScan::found_date)); - CHECK(check(date_end).contains(HashSourceCode::found_date)); + CHECK(check(date_end).contains(SourceCodeScan::found_date)); CHECK(check(date_end.substr(date_end.length() - 8)) - .contains(HashSourceCode::found_date)); + .contains(SourceCodeScan::found_date)); CHECK(check(date_end.substr(date_end.length() - 7)).empty()); - CHECK(check(timestamp_start).contains(HashSourceCode::found_timestamp)); + CHECK(check(timestamp_start).contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_start.substr(1)).empty()); - CHECK(check(timestamp_middle).contains(HashSourceCode::found_timestamp)); + CHECK(check(timestamp_middle).contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_middle.substr(1)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_middle.substr(2)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_middle.substr(3)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_middle.substr(4)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_middle.substr(5)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_middle.substr(6)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_middle.substr(7)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); - CHECK(check(timestamp_end).contains(HashSourceCode::found_timestamp)); + CHECK(check(timestamp_end).contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_end.substr(timestamp_end.length() - 13)) - .contains(HashSourceCode::found_timestamp)); + .contains(SourceCodeScan::found_timestamp)); CHECK(check(timestamp_end.substr(timestamp_end.length() - 12)).empty()); CHECK(check(no_temporal.substr(0)).empty()); @@ -299,4 +300,238 @@ TEST_CASE("check_for_temporal_macros") #endif } +TEST_CASE("check_for_source_code_patterns: temporal macros and directives") +{ + TestContext test_context; + + const std::string source = FMT( + "#em{0}bed \"first.bin\"\n" + "#em{0}bed RESOURCE\n" + ".inc{0}bin \"second.bin\"\n" + "const char* build_date = __DATE__;\n" + "const char* build_time = __TIME__;\n" + "const char* build_stamp = __TIMESTAMP__;\n", + ""); + + SourceCodeScanResult result; + + SUBCASE("scalar") + { + result = check_for_source_code_patterns_scalar(source); + } + +#ifdef HAVE_AVX2 + if (util::cpu_supports_avx2()) { + SUBCASE("avx2") + { + result = check_for_source_code_patterns_avx2(source); + } + } +#endif + + CHECK(result.contains(SourceCodeScan::found_embed)); + CHECK(result.contains(SourceCodeScan::found_incbin)); + CHECK(result.contains(SourceCodeScan::found_date)); + CHECK(result.contains(SourceCodeScan::found_time)); + CHECK(result.contains(SourceCodeScan::found_timestamp)); +} + +TEST_CASE("check_for_source_code_patterns: macro-expanded embed operand") +{ + TestContext test_context; + + const std::string source = FMT("#em{}bed RESOURCE\n", ""); + SourceCodeScanResult result; + + SUBCASE("scalar") + { + result = check_for_source_code_patterns_scalar(source); + } + +#ifdef HAVE_AVX2 + if (util::cpu_supports_avx2()) { + SUBCASE("avx2") + { + result = check_for_source_code_patterns_avx2(source); + } + } +#endif + + CHECK(result.contains(SourceCodeScan::found_embed)); +} + +#ifdef HAVE_AVX2 + +static bool +contains_embed_directive(std::string_view source) +{ + auto result = check_for_source_code_patterns_avx2(source); + return result.contains(SourceCodeScan::found_embed); +} + +static bool +contains_incbin_directive(std::string_view source) +{ + auto result = check_for_source_code_patterns_avx2(source); + return result.contains(SourceCodeScan::found_incbin); +} + +TEST_CASE("check_for_source_code_patterns_avx2: empty source" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK_FALSE(contains_embed_directive("")); +} + +TEST_CASE("check_for_source_code_patterns_avx2: no embed directives" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK_FALSE(contains_embed_directive(R"( +#include +#include "header.h" +int main() { return 0; } +)")); +} + +TEST_CASE("check_for_source_code_patterns_avx2: simple quoted embed" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("\n#em{}bed \"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: simple system embed" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("\n#em{}bed \n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: embed with path" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive( + FMT("\n#em{}bed \"assets/textures/icon.png\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: embed with parameters" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK( + contains_embed_directive(FMT("\n#em{}bed \"data.bin\" limit(100)\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: embed with multiple parameters" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK( + contains_embed_directive(FMT("\n#em{}bed \"data.bin\" prefix(0x00,)" + " suffix(,0x00) if_empty(0) limit(256)\n", + ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: multiple embeds" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK( + contains_embed_directive(FMT("\n#include \n" + "#em{0}bed \"file1.bin\"\n" + "int main() {{\n" + "#em{0}bed \"file2.bin\"\n" + "#em{0}bed \n" + " return 0;\n" + "}}\n", + ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: embed with whitespace" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("\n# em{}bed \"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: indented embed" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("\n #em{}bed \"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: embed after line continuation" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("#\\\nem{}bed \"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: embed with line continuation" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("#em{}bed \\\n\"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: embed at start of file" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("#em{}bed \"first.bin\"\n", ""))); +} + +TEST_CASE( + "check_for_source_code_patterns_avx2: embed at end of file without newline" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("#em{}bed \"last.bin\"", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: ignores embedded in identifier" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK_FALSE( + contains_embed_directive("#embedded \"not_this.bin\"\n" + "#embedx \"not_this_either.bin\"\n")); +} + +TEST_CASE("check_for_source_code_patterns_avx2: handles tabs" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_embed_directive(FMT("#\tem{}bed\t\"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: mixed includes and embeds" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK( + contains_embed_directive(FMT("#include \n" + "#include \"local.h\"\n" + "#em{0}bed \"binary.dat\"\n" + "#define FOO 1\n" + "#em{0}bed \n" + "#ifdef BAR\n" + "#em{0}bed \"conditional.bin\"\n" + "#endif\n", + ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: no incbin directive" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK_FALSE(contains_incbin_directive( + FMT("\n #include \n .inc{}bin data.bin\n ", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: simple incbin" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_incbin_directive(FMT(".inc{}bin \"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: incbin without space" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_incbin_directive(FMT(".inc{}bin\"data.bin\"\n", ""))); +} + +TEST_CASE("check_for_source_code_patterns_avx2: escaped quote" + * doctest::skip(!util::cpu_supports_avx2())) +{ + CHECK(contains_incbin_directive(FMT(".inc{}bin \\\"data.bin\\\"\n", ""))); +} + +#endif // HAVE_AVX2 + TEST_SUITE_END(); diff --git a/unittest/test_inodecache.cpp b/unittest/test_inodecache.cpp index ce285d4f..b128a9a6 100644 --- a/unittest/test_inodecache.cpp +++ b/unittest/test_inodecache.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2025 Joel Rosdahl and other contributors +// Copyright (C) 2020-2026 Joel Rosdahl and other contributors // // See doc/authors.adoc for a complete list of contributors. // @@ -67,14 +67,13 @@ init(Config& config) bool put(InodeCache& inode_cache, + InodeCache::ContentType content_type, const std::string& filename, const std::string& str, - HashSourceCodeResult return_value) + SourceCodeScanResult return_value) { - return inode_cache.put(filename, - InodeCache::ContentType::checked_for_temporal_macros, - Hash().hash(str).digest(), - return_value); + return inode_cache.put( + filename, content_type, Hash().hash(str).digest(), return_value); } } // namespace @@ -95,7 +94,7 @@ TEST_CASE("Test disabled") CHECK(!inode_cache.put("a", InodeCache::ContentType::checked_for_temporal_macros, Hash::Digest(), - HashSourceCodeResult())); + SourceCodeScanResult())); CHECK(inode_cache.get_hits() == -1); CHECK(inode_cache.get_misses() == -1); CHECK(inode_cache.get_errors() == -1); @@ -128,15 +127,19 @@ TEST_CASE("Test put and lookup") InodeCache inode_cache(config, 0ns); REQUIRE(util::write_file("a", "a text")); - HashSourceCodeResult result; - result.insert(HashSourceCode::found_date); - CHECK(put(inode_cache, "a", "a text", result)); + SourceCodeScanResult result; + result.insert(SourceCodeScan::found_date); + CHECK(put(inode_cache, + InodeCache::ContentType::checked_for_temporal_macros, + "a", + "a text", + result)); auto return_value = inode_cache.get("a", InodeCache::ContentType::checked_for_temporal_macros); REQUIRE(return_value); CHECK(return_value->first.to_bitmask() - == static_cast(HashSourceCode::found_date)); + == static_cast(SourceCodeScan::found_date)); CHECK(return_value->second == Hash().hash("a text").digest()); CHECK(inode_cache.get_hits() == 1); CHECK(inode_cache.get_misses() == 0); @@ -151,15 +154,16 @@ TEST_CASE("Test put and lookup") CHECK(inode_cache.get_errors() == 0); CHECK(put(inode_cache, + InodeCache::ContentType::checked_for_temporal_macros, "a", "something else", - HashSourceCodeResult(HashSourceCode::found_time))); + SourceCodeScanResult(SourceCodeScan::found_time))); return_value = inode_cache.get("a", InodeCache::ContentType::checked_for_temporal_macros); REQUIRE(return_value); CHECK(return_value->first.to_bitmask() - == static_cast(HashSourceCode::found_time)); + == static_cast(SourceCodeScan::found_time)); CHECK(return_value->second == Hash().hash("something else").digest()); CHECK(inode_cache.get_hits() == 2); CHECK(inode_cache.get_misses() == 1); @@ -197,23 +201,23 @@ TEST_CASE("Test content type") CHECK(inode_cache.put("a", InodeCache::ContentType::raw, binary_digest, - HashSourceCodeResult(HashSourceCode::found_date))); + SourceCodeScanResult(SourceCodeScan::found_date))); CHECK(inode_cache.put("a", InodeCache::ContentType::checked_for_temporal_macros, code_digest, - HashSourceCodeResult(HashSourceCode::found_time))); + SourceCodeScanResult(SourceCodeScan::found_time))); auto return_value = inode_cache.get("a", InodeCache::ContentType::raw); REQUIRE(return_value); CHECK(return_value->first.to_bitmask() - == static_cast(HashSourceCode::found_date)); + == static_cast(SourceCodeScan::found_date)); CHECK(return_value->second == binary_digest); return_value = inode_cache.get("a", InodeCache::ContentType::checked_for_temporal_macros); REQUIRE(return_value); CHECK(return_value->first.to_bitmask() - == static_cast(HashSourceCode::found_time)); + == static_cast(SourceCodeScan::found_time)); CHECK(return_value->second == code_digest); }