[libc++] Make sure we don't cache DSL functions too aggressively
authorLouis Dionne <ldionne@apple.com>
Fri, 9 Oct 2020 14:08:02 +0000 (10:08 -0400)
committerLouis Dionne <ldionne@apple.com>
Fri, 9 Oct 2020 14:22:46 +0000 (10:22 -0400)
To make sure we don't store a mutable object (which could be modified by
outside code without us noticing) as the cache key, we pickle the cache
key to get a byte stream. If two keys are unequal, we know for sure they
will not have the same pickling. And if they are equal, there's a large
chance they will have the same pickling. If they don't, we might end up
not reusing a cached entry when we could have, but at least the behavior
we'll have is semantically correct.

libcxx/test/libcxx/selftest/dsl/dsl.sh.py
libcxx/utils/libcxx/test/dsl.py

index 3c90ebe..d20781a 100644 (file)
@@ -85,6 +85,18 @@ class SetupConfigs(unittest.TestCase):
         return found[0]
 
 
+def findIndex(list, pred):
+    """Finds the index of the first element satisfying 'pred' in a list, or
+       'len(list)' if there is no such element."""
+    index = 0
+    for x in list:
+        if pred(x):
+            break
+        else:
+            index += 1
+    return index
+
+
 class TestHasCompileFlag(SetupConfigs):
     """
     Tests for libcxx.test.dsl.hasCompileFlag
@@ -172,6 +184,29 @@ class TestProgramOutput(SetupConfigs):
         args = ["first-argument", "second-argument"]
         self.assertEqual(dsl.programOutput(self.config, source, args=args), "")
 
+    def test_caching_is_not_too_aggressive(self):
+        # Run a program, then change the substitutions and run it again.
+        # Make sure the program is run the second time and the right result
+        # is given, to ensure we're not incorrectly caching the result of the
+        # first program run.
+        source = """
+        #include <cstdio>
+        int main(int, char**) {
+            std::printf("MACRO=%u\\n", MACRO);
+        }
+        """
+        compileFlagsIndex = findIndex(self.config.substitutions, lambda x: x[0] == '%{compile_flags}')
+        compileFlags = self.config.substitutions[compileFlagsIndex][1]
+
+        self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}',  compileFlags + ' -DMACRO=1')
+        output1 = dsl.programOutput(self.config, source)
+        self.assertEqual(output1, "MACRO=1\n")
+
+        self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}',  compileFlags + ' -DMACRO=2')
+        output2 = dsl.programOutput(self.config, source)
+        self.assertEqual(output2, "MACRO=2\n")
+
+
 class TestHasLocale(SetupConfigs):
     """
     Tests for libcxx.test.dsl.hasLocale
index ecbb59e..78f9841 100644 (file)
@@ -7,6 +7,7 @@
 #===----------------------------------------------------------------------===##
 
 import os
+import pickle
 import pipes
 import platform
 import re
@@ -24,21 +25,17 @@ def _memoizeExpensiveOperation(extractCacheKey):
   """
   Allows memoizing a very expensive operation.
 
-  The caching is implemented as a list, and we search linearly for existing
-  entries. This is incredibly naive, however this allows the cache keys to
-  contain lists and other non-hashable objects. Assuming the operation we're
-  memoizing is very expensive, this is still a win anyway.
+  We pickle the cache key to make sure we store an immutable representation
+  of it. If we stored an object and the object was referenced elsewhere, it
+  could be changed from under our feet, which would break the cache.
   """
   def decorator(function):
-    cache = []
+    cache = {}
     def f(*args, **kwargs):
-      cacheKey = extractCacheKey(*args, **kwargs)
-      try:
-        result = next(res for (key, res) in cache if key == cacheKey)
-      except StopIteration: # This wasn't in the cache
-        result = function(*args, **kwargs)
-        cache.append((cacheKey, result))
-      return result
+      cacheKey = pickle.dumps(extractCacheKey(*args, **kwargs))
+      if cacheKey not in cache:
+        cache[cacheKey] = function(*args, **kwargs)
+      return cache[cacheKey]
     return f
   return decorator