]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Don’t cache result if a preprocessed header file is too new
authorJoel Rosdahl <joel@rosdahl.net>
Sun, 17 Jan 2021 12:07:01 +0000 (13:07 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 17 Jan 2021 14:48:51 +0000 (15:48 +0100)
Unless sloppiness is set to ignore mtime/ctime, ccache classifies a
newly created header file as “too new” and then skips it in
do_remember_include_file and returns false, which makes
remember_include_file disable the direct mode. This works as intended
for normal header files whose content is included in the preprocessed
output. However, for a preprocessed header file this doesn’t work well
since its content then isn’t included in the hash, which can lead to
false positive cache hits.

Fix this by not caching the result if a preprocessed header file is too
new, i.e. increment the “can’t use preprocessed header” statistics
counter and fall back to just running the compiler.

Closes #774.

doc/MANUAL.adoc
src/ccache.cpp
test/suites/pch.bash

index ab57362643a08cc97d88587b80ad3b40b5c5837b..fbbf0822218dda6f8d1a3bd97e6fecf54ec80e52 100644 (file)
@@ -815,11 +815,15 @@ still has to do _some_ preprocessing (like macros).
     Ignore ctimes when *file_stat_matches* is enabled. This can be useful when
     backdating files' mtimes in a controlled way.
 *include_file_ctime*::
-    By default, ccache will not cache a file if it includes a header whose
-    ctime is too new. This sloppiness disables that check.
+    By default, ccache will not cache a file if it includes a header whose ctime
+    is too new. This sloppiness disables that check. See also
+    _<<_handling_of_newly_created_header_files,Handling of newly created header
+    files>>_.
 *include_file_mtime*::
     By default, ccache will not cache a file if it includes a header whose
-    mtime is too new. This sloppiness disables that check.
+    mtime is too new. This sloppiness disables that check. See also
+    _<<_handling_of_newly_created_header_files,Handling of newly created header
+    files>>_.
 *locale*::
     Ccache includes the environment variables *LANG*, *LC_ALL*, *LC_CTYPE* and
     *LC_MESSAGES* in the hash by default since they may affect localization of
@@ -1229,6 +1233,34 @@ The depend mode will be disabled if any of the following holds:
 * The compiler is not generating dependencies using *-MD* or *-MMD*.
 
 
+Handling of newly created header files
+--------------------------------------
+
+If modification time (mtime) or status change time (ctime) of one of the include
+files is the same second as the time compilation is being done, ccache disables
+the direct mode (or, in the case of a <<_precompiled_headers,precompiled
+header>>, disables caching completely). This done as a safety measure to avoid a
+race condition (see below).
+
+To be able to use a newly created header files in direct mode (or use a newly
+precompiled header), either:
+
+* create the include file earlier in the build process, or
+* set <<config_sloppiness,*sloppiness*>> to
+  *include_file_ctime,include_file_mtime* if you are willing to take the risk,
+  for instance if you know that your build system is robust enough not to
+  trigger the race condition.
+
+For reference, the race condition mentioned above consists of these events:
+
+1. The preprocessor is run.
+2. An include file is modified by someone.
+3. The new include file is hashed by ccache.
+4. The real compiler is run on the preprocessor's output, which contains data
+   from the old header file.
+5. The wrong object file is stored in the cache.
+
+
 Cache debugging
 ---------------
 
@@ -1333,6 +1365,10 @@ things to make it work properly:
   `__TIMESTAMP__` is used when using a precompiled header. Further, it can't
   detect changes in **#define**s in the source code because of how
   preprocessing works in combination with precompiled headers.
+* You may also want to include *include_file_mtime,include_file_ctime* in
+  <<config_sloppiness,*sloppiness*>>. See
+  _<<_handling_of_newly_created_header_files,Handling of newly created header
+  files>>_.
 * You must either:
 +
 --
@@ -1503,15 +1539,10 @@ problems and what may be done to increase the hit rate:
    *-Wp,-MMD,_path_*, and *-Wp,-D_define_*) is used.
 ** This was the first compilation with a new value of the
    <<config_base_dir,base directory>>.
-** A modification time of one of the include files is too new (created the same
-   second as the compilation is being done). This check is made to avoid a race
-   condition. To fix this, create the include file earlier in the build
-   process, if possible, or set <<config_sloppiness,*sloppiness*>> to
-   *include_file_ctime, include_file_mtime* if you are willing to take the risk.
-   (The race condition consists of these events: the preprocessor is run; an
-   include file is modified by someone; the new include file is hashed by
-   ccache; the real compiler is run on the preprocessor's output, which contains
-   data from the old header file; the wrong object file is stored in the cache.)
+** A modification or status change time of one of the include files is too new
+   (created the same second as the compilation is being done). See
+   _<<_handling_of_newly_created_header_files,Handling of newly created header
+   files>>_.
 ** The `__TIME__` preprocessor macro is (potentially) being used. Ccache turns
    off direct mode if `__TIME__` is present in the source code. This is done as
    a safety measure since the string indicates that a `__TIME__` macro _may_
index d3a0119e19bf1074fd5efc126d9933d2c284e1fc..eabe6846c90b639852f8ddfcf89af12c772ad01a 100644 (file)
@@ -462,20 +462,28 @@ do_remember_include_file(Context& ctx,
   return true;
 }
 
+enum class RememberIncludeFileResult { ok, cannot_use_pch };
+
 // This function hashes an include file and stores the path and hash in
 // ctx.included_files. If the include file is a PCH, cpp_hash is also updated.
-static void
+static RememberIncludeFileResult
 remember_include_file(Context& ctx,
                       const std::string& path,
                       Hash& cpp_hash,
                       bool system,
                       Hash* depend_mode_hash)
 {
-  if (!do_remember_include_file(ctx, path, cpp_hash, system, depend_mode_hash)
-      && ctx.config.direct_mode()) {
-    LOG_RAW("Disabling direct mode");
-    ctx.config.set_direct_mode(false);
+  if (!do_remember_include_file(
+        ctx, path, cpp_hash, system, depend_mode_hash)) {
+    if (Util::is_precompiled_header(path)) {
+      return RememberIncludeFileResult::cannot_use_pch;
+    } else if (ctx.config.direct_mode()) {
+      LOG_RAW("Disabling direct mode");
+      ctx.config.set_direct_mode(false);
+    }
   }
+
+  return RememberIncludeFileResult::ok;
 }
 
 static void
@@ -492,7 +500,10 @@ print_included_files(const Context& ctx, FILE* fp)
 // - Makes include file paths for which the base directory is a prefix relative
 //   when computing the hash sum.
 // - Stores the paths and hashes of included files in ctx.included_files.
-static bool
+//
+// Returns Statistic::none on success, otherwise a statistics counter to be
+// incremented.
+static Statistic
 process_preprocessed_file(Context& ctx,
                           Hash& hash,
                           const std::string& path,
@@ -502,7 +513,7 @@ process_preprocessed_file(Context& ctx,
   try {
     data = Util::read_file(path);
   } catch (Error&) {
-    return false;
+    return Statistic::internal_error;
   }
 
   // Bytes between p and q are pending to be hashed.
@@ -583,7 +594,7 @@ process_preprocessed_file(Context& ctx,
       q++;
       if (q >= end) {
         LOG_RAW("Failed to parse included file path");
-        return false;
+        return Statistic::internal_error;
       }
       // q points to the beginning of an include file path
       hash.hash(p, q - p);
@@ -625,7 +636,10 @@ process_preprocessed_file(Context& ctx,
         hash.hash(inc_path);
       }
 
-      remember_include_file(ctx, inc_path, hash, system, nullptr);
+      if (remember_include_file(ctx, inc_path, hash, system, nullptr)
+          == RememberIncludeFileResult::cannot_use_pch) {
+        return Statistic::could_not_use_precompiled_header;
+      }
       p = q; // Everything of interest between p and q has been hashed now.
     } else if (q[0] == '.' && q[1] == 'i' && q[2] == 'n' && q[3] == 'c'
                && q[4] == 'b' && q[5] == 'i' && q[6] == 'n') {
@@ -670,7 +684,7 @@ process_preprocessed_file(Context& ctx,
     print_included_files(ctx, stdout);
   }
 
-  return true;
+  return Statistic::none;
 }
 
 // Extract the used includes from the dependency file. Note that we cannot
@@ -1163,9 +1177,11 @@ get_result_name_from_cpp(Context& ctx, Args& args, Hash& hash)
   }
 
   hash.hash_delimiter("cpp");
-  bool is_pump = ctx.config.compiler_type() == CompilerType::pump;
-  if (!process_preprocessed_file(ctx, hash, stdout_path, is_pump)) {
-    throw Failure(Statistic::internal_error);
+  const bool is_pump = ctx.config.compiler_type() == CompilerType::pump;
+  const Statistic error =
+    process_preprocessed_file(ctx, hash, stdout_path, is_pump);
+  if (error != Statistic::none) {
+    throw Failure(error);
   }
 
   hash.hash_delimiter("cppstderr");
index 5729575b9eb42012f4e3e11faba6ee026c48f89e..97a5c5e0a7c6b3284a244f7fa0681cd95e68a351 100644 (file)
@@ -609,6 +609,29 @@ pch_suite_gcc() {
     expect_stat 'cache hit (direct)' 2
     expect_stat 'cache hit (preprocessed)' 0
     expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "Too new PCH file"
+
+    # If the precompiled header is too new we shouldn't cache the result at all
+    # since:
+    #
+    # - the precompiled header content must be included in the hash, but
+    # - we don't trust the precompiled header content so we can't hash it
+    #   ourselves, and
+    # - the preprocessed output doesn't contain the preprocessed header content.
+
+    touch lib.h
+    touch main.c
+
+    $REAL_COMPILER $SYSROOT -c lib.h
+    touch -d "@$(($(date +%s) + 60))" lib.h.gch # 1 minute in the future
+
+    CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines,time_macros" $CCACHE_COMPILE -include lib.h -c main.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat "can't use precompiled header" 1
 }
 
 pch_suite_clang() {