From b5b9cf3a424ed32e0495aedd2376a0ffdf4a967d Mon Sep 17 00:00:00 2001
From: "Edward Z. Yang" <ezyang@cs.stanford.edu>
Date: Thu, 17 Nov 2016 17:08:21 -0500
Subject: [PATCH] Remove fancy shadowing logic; always override in package
 database order.

This is a stopgap fix for GHC 8.0 bug #12485: in particular,
it relaxes need for -package-db flags to be given in
dependency order.  The trade-off is that we are a lot more
unsafe when there are packages with duplicate 'id's in
the database stack: the new code will not do an ABI compatibility
check: if two packages have the same 'id', they are assumed to
be ABI compatible.  If this is not true, GHC may build
segfaulting executables.

Missing test updates, but I'm putting it up so people can
take a look.

In GHC 8.2, we'll record ABIs for all dependencies, allowing
GHC to make better decisions about shadowing.

Signed-off-by: Edward Z. Yang <ezyang@cs.stanford.edu>

Test Plan: validate

Reviewers: austin, niteria, bgamari, simonmar

Reviewed By: simonmar

Subscribers: thomie

Differential Revision: https://phabricator.haskell.org/D2613

GHC Trac Issues: #12485
---
 compiler/main/Packages.hs           | 121 +++++++++++-----------------
 docs/users_guide/packages.rst       |   8 +-
 testsuite/tests/cabal/Makefile      |  22 +++--
 testsuite/tests/cabal/cabal08/all.T |   4 +-
 testsuite/tests/cabal/shadow.stderr |   4 -
 testsuite/tests/cabal/shadow.stdout |   4 +-
 6 files changed, 67 insertions(+), 96 deletions(-)

diff --git a/compiler/main/Packages.hs b/compiler/main/Packages.hs
index b44106bde67f..4de9b46a9a31 100644
--- a/compiler/main/Packages.hs
+++ b/compiler/main/Packages.hs
@@ -816,10 +816,9 @@ updateVisibilityMap wiredInMap vis_map = foldl' f vis_map (Map.toList wiredInMap
 
 -- ----------------------------------------------------------------------------
 
-type IsShadowed = Bool
 data UnusablePackageReason
   = IgnoredWithFlag
-  | MissingDependencies IsShadowed [UnitId]
+  | MissingDependencies [UnitId]
 
 type UnusablePackages = Map UnitId
                             (PackageConfig, UnusablePackageReason)
@@ -828,11 +827,8 @@ pprReason :: SDoc -> UnusablePackageReason -> SDoc
 pprReason pref reason = case reason of
   IgnoredWithFlag ->
       pref <+> text "ignored due to an -ignore-package flag"
-  MissingDependencies is_shadowed deps ->
-      pref <+> text "unusable due to"
-           <+> (if is_shadowed then text "shadowed"
-                               else text "missing or recursive")
-           <+> text "dependencies:" $$
+  MissingDependencies deps ->
+      pref <+> text "unusable due to missing or recursive dependencies:" $$
         nest 2 (hsep (map ppr deps))
 
 reportUnusable :: DynFlags -> UnusablePackages -> IO ()
@@ -851,16 +847,15 @@ reportUnusable dflags pkgs = mapM_ report (Map.toList pkgs)
 -- dependency graph, repeatedly adding packages whose dependencies are
 -- satisfied until no more can be added.
 --
-findBroken :: IsShadowed
-           -> [PackageConfig]
+findBroken :: [PackageConfig]
            -> Map UnitId PackageConfig
            -> UnusablePackages
-findBroken is_shadowed pkgs pkg_map0 = go [] pkg_map0 pkgs
+findBroken pkgs pkg_map0 = go [] pkg_map0 pkgs
  where
    go avail pkg_map not_avail =
      case partitionWith (depsAvailable pkg_map) not_avail of
         ([], not_avail) ->
-            Map.fromList [ (unitId p, (p, MissingDependencies is_shadowed deps))
+            Map.fromList [ (unitId p, (p, MissingDependencies deps))
                          | (p,deps) <- not_avail ]
         (new_avail, not_avail) ->
             go (new_avail ++ avail) pkg_map' (map fst not_avail)
@@ -962,71 +957,53 @@ mkPackageState dflags dbs preload0 = do
   let other_flags = reverse (packageFlags dflags)
       ignore_flags = reverse (ignorePackageFlags dflags)
 
-  let merge (pkg_map, prev_unusable) (db_path, db) = do
+  let merge pkg_map (db_path, db) = do
+        debugTraceMsg dflags 2 $
+            text "loading package database" <+> text db_path
+        forM_ (Set.toList override_set) $ \pkg ->
             debugTraceMsg dflags 2 $
-                text "loading package database" <+> text db_path
-            forM_ (Set.toList shadow_set) $ \pkg ->
-                debugTraceMsg dflags 2 $
-                    text "package" <+> ppr pkg <+>
-                    text "shadows a previously defined package"
-            reportUnusable dflags unusable
-            -- NB: an unusable unit ID can become usable again
-            -- if it's validly specified in a later package stack.
-            -- Keep unusable up-to-date!
-            return (pkg_map', (prev_unusable `Map.difference` pkg_map')
-                                    `Map.union` unusable)
-        where -- The set of UnitIds which appear in both
-              -- db and pkgs (to be shadowed from pkgs)
-              shadow_set :: Set UnitId
-              shadow_set = foldr ins Set.empty db
-                where ins pkg s
-                        -- If the package from the upper database is
-                        -- in the lower database, and the ABIs don't
-                        -- match...
-                        | Just old_pkg <- Map.lookup (unitId pkg) pkg_map
-                        , abiHash old_pkg /= abiHash pkg
-                        -- ...add this unit ID to the set of unit IDs
-                        -- which (transitively) should be shadowed from
-                        -- the lower database.
-                        = Set.insert (unitId pkg) s
-                        | otherwise
-                        = s
-              -- Remove shadow_set from pkg_map...
-              shadowed_pkgs0 :: [PackageConfig]
-              shadowed_pkgs0 = filter (not . (`Set.member` shadow_set) . unitId)
-                                      (Map.elems pkg_map)
-              -- ...and then remove anything transitively broken
-              -- this way.
-              shadowed = findBroken True shadowed_pkgs0 Map.empty
-              shadowed_pkgs :: [PackageConfig]
-              shadowed_pkgs = filter (not . (`Map.member` shadowed) . unitId)
-                                     shadowed_pkgs0
-
-              -- Apply ignore flags to db (TODO: could extend command line
-              -- flag format to support per-database ignore now!  More useful
-              -- than what we have now.)
-              ignored = ignorePackages ignore_flags db
-              db2 = filter (not . (`Map.member` ignored) . unitId) db
-
-              -- Look for broken packages (either from ignore, or possibly
-              -- because the db was broken to begin with)
-              mk_pkg_map = Map.fromList . map (\p -> (unitId p, p))
-              broken = findBroken False db2 (mk_pkg_map shadowed_pkgs)
-              db3 = filter (not . (`Map.member` broken) . unitId) db2
-
-              unusable = shadowed `Map.union` ignored
-                                  `Map.union` broken
-
-              -- Now merge the sets together (NB: later overrides
-              -- earlier!)
-              pkg_map' :: Map UnitId PackageConfig
-              pkg_map' = mk_pkg_map (shadowed_pkgs ++ db3)
-
-  (pkg_map1, unusable) <- foldM merge (Map.empty, Map.empty) dbs
+                text "package" <+> ppr pkg <+>
+                text "overrides a previously defined package"
+        return pkg_map'
+       where
+        db_map = mk_pkg_map db
+        mk_pkg_map = Map.fromList . map (\p -> (unitId p, p))
+
+        -- The set of UnitIds which appear in both db and pkgs.  These are the
+        -- ones that get overridden.  Compute this just to give some
+        -- helpful debug messages at -v2
+        override_set :: Set UnitId
+        override_set = Set.intersection (Map.keysSet db_map)
+                                        (Map.keysSet pkg_map)
+
+        -- Now merge the sets together (NB: in case of duplicate,
+        -- first argument preferred)
+        pkg_map' :: Map UnitId PackageConfig
+        pkg_map' = Map.union db_map pkg_map
+
+  pkg_map1 <- foldM merge Map.empty dbs
+
+  -- Now that we've merged everything together, prune out unusable
+  -- packages.
+  let pkgs01 = Map.elems pkg_map1
+
+      -- First, apply ignore flags to db
+      ignored = ignorePackages ignore_flags pkgs01
+      pkgs02 = filter (not . (`Map.member` ignored) . unitId) pkgs01
+
+      -- Look for broken packages (either from ignore, or possibly
+      -- because the db was broken to begin with)
+      broken = findBroken pkgs02 Map.empty
+      pkgs03 = filter (not . (`Map.member` broken) . unitId) pkgs02
+
+      unusable = ignored `Map.union` broken
+
+  reportUnusable dflags unusable
+
   -- Apply trust flags (these flags apply regardless of whether
   -- or not packages are visible or not)
   pkgs1 <- foldM (applyTrustFlag dflags unusable)
-                 (Map.elems pkg_map1) (reverse (trustFlags dflags))
+                 pkgs03 (reverse (trustFlags dflags))
 
   --
   -- Calculate the initial set of packages, prior to any package flags.
diff --git a/docs/users_guide/packages.rst b/docs/users_guide/packages.rst
index d3da125d587a..9c577fbfaa67 100644
--- a/docs/users_guide/packages.rst
+++ b/docs/users_guide/packages.rst
@@ -392,10 +392,10 @@ to packages below, but not vice versa).
 *Package shadowing:* When multiple package databases are in use it
 is possible, though rarely, that the same installed package id is present in
 more than one database. In that case, packages closer to the top of the stack
-will override (*shadow*) those below them. If the conflicting packages are
-found to be equivalent (by ABI hash comparison) then one of them replaces all
-references to the other, otherwise the overridden package and all those
-depending on it will be removed.
+will *override* those below them.  Shadowing is an *unsafe* operation:
+if a package overrides another package it is not ABI compatible with,
+it is possible that GHC will end up with an inconsistent view of the
+package database that could induce it to build segfaulting executables.
 
 *Package version selection:* When selecting a package, GHC will search for
 packages in all available databases. If multiple versions of the same package
diff --git a/testsuite/tests/cabal/Makefile b/testsuite/tests/cabal/Makefile
index cbf8cbb7ed0b..4115a6df726e 100644
--- a/testsuite/tests/cabal/Makefile
+++ b/testsuite/tests/cabal/Makefile
@@ -134,14 +134,11 @@ LOCAL_GHC_PKGSHADOW3 = '$(GHC_PKG)' --no-user-package-db -f $(PKGCONFSHADOW3)
 LOCAL_GHC_PKGSHADOW12 = '$(GHC_PKG)' --no-user-package-db -f $(PKGCONFSHADOW1) -f $(PKGCONFSHADOW2)
 LOCAL_GHC_PKGSHADOW13 = '$(GHC_PKG)' --no-user-package-db -f $(PKGCONFSHADOW1) -f $(PKGCONFSHADOW3)
 
-# Test package shadowing behaviour.
+# Test package overriding behaviour.
 #
 #     localshadow1.package.conf:  shadowdep-1-XXX <- shadow-1-XXX
 #     localshadow2.package.conf:                     shadow-1-XXX
 #
-# If the ABI hash of boths shadow-1s are the same, we'll just accept
-# the later shadow version.  However, if the ABIs are different, we
-# should complain!
 shadow:
 	rm -rf $(PKGCONFSHADOW1) $(PKGCONFSHADOW2) $(PKGCONFSHADOW3) shadow.hs shadow.o shadow.hi shadow.out shadow.hs shadow.hi
 	$(LOCAL_GHC_PKGSHADOW1) init $(PKGCONFSHADOW1)
@@ -150,7 +147,7 @@ shadow:
 	$(LOCAL_GHC_PKGSHADOW1) register -v0 --force shadow1.pkg
 	$(LOCAL_GHC_PKGSHADOW1) register -v0 --force shadow2.pkg
 	$(LOCAL_GHC_PKGSHADOW2) register -v0 --force shadow3.pkg
-	$(LOCAL_GHC_PKGSHADOW3) register -v0 --force shadow1.pkg
+	$(LOCAL_GHC_PKGSHADOW3) register -v0 --force shadow2.pkg
 	@echo "databases 1 and 2:"
 	$(LOCAL_GHC_PKGSHADOW12) list
 	@echo "databases 1 and 3:"
@@ -158,22 +155,21 @@ shadow:
 	echo "main = return ()" >shadow.hs
 #
 # In this test, the later database defines a new shadow-1-XXX which
-# shadows the old one, making shadowdep unsatisfiable.
+# overrides the old one.
 #
-	@echo "should FAIL:"
-	if '$(TEST_HC)' $(TEST_HC_OPTS) -package-db $(PKGCONFSHADOW1) -package-db $(PKGCONFSHADOW2) -package shadowdep -c shadow.hs -fno-code; then false; else true; fi
+	@echo "should SUCCEED:"
+	'$(TEST_HC)' $(TEST_HC_OPTS) -package-db $(PKGCONFSHADOW1) -package-db $(PKGCONFSHADOW2) -package shadowdep -c shadow.hs -fno-code
 #
-# Reversing the orders of the configs fixes the problem, because now
-# the shadow-1-XXX defined in the same DB as shadowdep shadows
-# shadow-1-XXX in localshadow2.package.conf
+# Reversing the orders of the configs also works; this time the copy from
+# db1 wins out.
 #
 	@echo "should SUCCEED:"
 	'$(TEST_HC)' $(TEST_HC_OPTS) -package-db $(PKGCONFSHADOW2) -package-db $(PKGCONFSHADOW1) -package shadowdep -c shadow.hs -fno-code
 #
-# When the ABIs are the same, dependencies don't break, we just swap it in
+# We can pass DBs out of order
 #
 	@echo "should SUCCEED:"
-	'$(TEST_HC)' $(TEST_HC_OPTS) -package-db $(PKGCONFSHADOW3) -package-db $(PKGCONFSHADOW1) -package shadowdep -c shadow.hs -fno-code
+	'$(TEST_HC)' $(TEST_HC_OPTS) -package-db $(PKGCONFSHADOW3) -package-db $(PKGCONFSHADOW2) -package shadowdep -c shadow.hs -fno-code
 
 # If we pass --global, we should ignore instances in the user database
 T5442a:
diff --git a/testsuite/tests/cabal/cabal08/all.T b/testsuite/tests/cabal/cabal08/all.T
index fc4221a769a9..a0ca1d855bed 100644
--- a/testsuite/tests/cabal/cabal08/all.T
+++ b/testsuite/tests/cabal/cabal08/all.T
@@ -3,7 +3,9 @@ if default_testopts.cleanup != '':
 else:
    cleanup = ''
 
+# This test is broken since we eliminated the fancy shadowing logic; skip it for
+# now. See #12485.
 test('cabal08',
-     normal,
+     skip,
      run_command,
      ['$MAKE -s --no-print-directory cabal08 ' + cleanup])
diff --git a/testsuite/tests/cabal/shadow.stderr b/testsuite/tests/cabal/shadow.stderr
index 601e33714f28..e69de29bb2d1 100644
--- a/testsuite/tests/cabal/shadow.stderr
+++ b/testsuite/tests/cabal/shadow.stderr
@@ -1,4 +0,0 @@
-<command line>: cannot satisfy -package shadowdep: 
-    shadowdep-1-XXX is unusable due to shadowed dependencies:
-      shadow-1-XXX
-    (use -v for more information)
diff --git a/testsuite/tests/cabal/shadow.stdout b/testsuite/tests/cabal/shadow.stdout
index a47ce15795d0..1f3a54684729 100644
--- a/testsuite/tests/cabal/shadow.stdout
+++ b/testsuite/tests/cabal/shadow.stdout
@@ -12,8 +12,8 @@ localshadow1.package.conf
     (shadowdep-1)
 
 localshadow3.package.conf
-    (shadow-1)
+    (shadowdep-1)
 
-should FAIL:
+should SUCCEED:
 should SUCCEED:
 should SUCCEED:
-- 
GitLab