langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java
changeset 31115 8d8e98052d5d
parent 27852 2e6ad0e4fe20
child 32335 7df616378cf3
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java	Tue Jun 09 11:52:13 2015 +0200
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/JavacState.java	Tue Jun 09 15:57:45 2015 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,11 +25,17 @@
 
 package com.sun.tools.sjavac;
 
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
 import java.net.URI;
 import java.nio.file.NoSuchFileException;
 import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -37,8 +43,10 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import com.sun.tools.sjavac.options.Options;
+import com.sun.tools.sjavac.pubapi.PubApi;
 import com.sun.tools.sjavac.server.Sjavac;
 
 /**
@@ -268,24 +276,25 @@
      * Save the javac_state file.
      */
     public void save() throws IOException {
-        if (!needsSaving) return;
+        if (!needsSaving)
+            return;
         try (FileWriter out = new FileWriter(javacState)) {
             StringBuilder b = new StringBuilder();
             long millisNow = System.currentTimeMillis();
             Date d = new Date(millisNow);
-            SimpleDateFormat df =
-                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
-            b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n");
+            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
+            b.append("# javac_state ver 0.4 generated "+millisNow+" "+df.format(d)+"\n");
             b.append("# This format might change at any time. Please do not depend on it.\n");
+            b.append("# R arguments\n");
             b.append("# M module\n");
             b.append("# P package\n");
             b.append("# S C source_tobe_compiled timestamp\n");
             b.append("# S L link_only_source timestamp\n");
             b.append("# G C generated_source timestamp\n");
             b.append("# A artifact timestamp\n");
-            b.append("# D dependency\n");
+            b.append("# D S dependant -> source dependency\n");
+            b.append("# D C dependant -> classpath dependency\n");
             b.append("# I pubapi\n");
-            b.append("# R arguments\n");
             b.append("R ").append(theArgs).append("\n");
 
             // Copy over the javac_state for the packages that did not need recompilation.
@@ -312,6 +321,8 @@
         boolean newCommandLine = false;
         boolean syntaxError = false;
 
+        Log.debug("Loading javac state file: " + db.javacState);
+
         try (BufferedReader in = new BufferedReader(new FileReader(db.javacState))) {
             for (;;) {
                 String l = in.readLine();
@@ -327,11 +338,14 @@
                     } else
                     if (c == 'D') {
                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
-                        lastPackage.loadDependency(l);
+                        char depType = l.charAt(2);
+                        if (depType != 'S' && depType != 'C')
+                            throw new RuntimeException("Bad dependency string: " + l);
+                        lastPackage.parseAndAddDependency(l.substring(4), depType == 'C');
                     } else
                     if (c == 'I') {
                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
-                        lastPackage.loadPubapi(l);
+                        lastPackage.getPubApi().appendItem(l.substring(2)); // Strip "I "
                     } else
                     if (c == 'A') {
                         if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
@@ -356,7 +370,7 @@
                             int sp = l.indexOf(" ", 18);
                             if (sp != -1) {
                                 String ver = l.substring(18,sp);
-                                if (!ver.equals("0.3")) {
+                                if (!ver.equals("0.4")) {
                     break;
                                  }
                 foundCorrectVerNr = true;
@@ -488,11 +502,92 @@
      * Propagate recompilation through the dependency chains.
      * Avoid re-tainting packages that have already been compiled.
      */
-    public void taintPackagesDependingOnChangedPackages(Set<String> pkgs, Set<String> recentlyCompiled) {
+    public void taintPackagesDependingOnChangedPackages(Set<String> pkgsWithChangedPubApi, Set<String> recentlyCompiled) {
+        // For each to-be-recompiled-candidates...
+        for (Package pkg : new HashSet<>(prev.packages().values())) {
+            // Find out what it depends upon...
+            Set<String> deps = pkg.typeDependencies()
+                                  .values()
+                                  .stream()
+                                  .flatMap(s -> s.stream())
+                                  .collect(Collectors.toSet());
+            for (String dep : deps) {
+                String depPkg = ":" + dep.substring(0, dep.lastIndexOf('.'));
+                if (depPkg.equals(pkg.name()))
+                    continue;
+                // Checking if that dependency has changed
+                if (pkgsWithChangedPubApi.contains(depPkg) && !recentlyCompiled.contains(pkg.name())) {
+                    taintPackage(pkg.name(), "its depending on " + depPkg);
+                }
+            }
+        }
+    }
+
+    /**
+     * Compare the javac_state recorded public apis of packages on the classpath
+     * with the actual public apis on the classpath.
+     */
+    public void taintPackagesDependingOnChangedClasspathPackages() {
+
+        // 1. Collect fully qualified names of all interesting classpath dependencies
+        Set<String> fqDependencies = new HashSet<>();
         for (Package pkg : prev.packages().values()) {
-            for (String dep : pkg.dependencies()) {
-                if (pkgs.contains(dep) && !recentlyCompiled.contains(pkg.name())) {
-                    taintPackage(pkg.name(), " its depending on "+dep);
+            // Check if this package was compiled. If it's presence is recorded
+            // because it was on the class path and we needed to save it's
+            // public api, it's not a candidate for tainting.
+            if (pkg.sources().isEmpty())
+                continue;
+
+            pkg.typeClasspathDependencies().values().forEach(fqDependencies::addAll);
+        }
+
+        // 2. Extract the public APIs from the on disk .class files
+        // (Reason for doing step 1 in a separate phase is to avoid extracting
+        // public APIs of the same class twice.)
+        PubApiExtractor pubApiExtractor = new PubApiExtractor(options);
+        Map<String, PubApi> onDiskPubApi = new HashMap<>();
+        for (String cpDep : fqDependencies) {
+            onDiskPubApi.put(cpDep, pubApiExtractor.getPubApi(cpDep));
+        }
+
+        // 3. Compare them with the public APIs as of last compilation (loaded from javac_state)
+        nextPkg:
+        for (Package pkg : prev.packages().values()) {
+            // Check if this package was compiled. If it's presence is recorded
+            // because it was on the class path and we needed to save it's
+            // public api, it's not a candidate for tainting.
+            if (pkg.sources().isEmpty())
+                continue;
+
+            Set<String> cpDepsOfThisPkg = new HashSet<>();
+            for (Set<String> cpDeps : pkg.typeClasspathDependencies().values())
+                cpDepsOfThisPkg.addAll(cpDeps);
+
+            for (String fqDep : cpDepsOfThisPkg) {
+
+                String depPkg = ":" + fqDep.substring(0, fqDep.lastIndexOf('.'));
+                PubApi prevPkgApi = prev.packages().get(depPkg).getPubApi();
+
+                // This PubApi directly lists the members of the class,
+                // i.e. [ MEMBER1, MEMBER2, ... ]
+                PubApi prevDepApi = prevPkgApi.types.get(fqDep).pubApi;
+
+                // In order to dive *into* the class, we need to add
+                // .types.get(fqDep).pubApi below.
+                PubApi currentDepApi = onDiskPubApi.get(fqDep).types.get(fqDep).pubApi;
+
+                if (!currentDepApi.isBackwardCompatibleWith(prevDepApi)) {
+                    List<String> apiDiff = currentDepApi.diff(prevDepApi);
+                    taintPackage(pkg.name(), "depends on classpath "
+                                + "package which has an updated package api: "
+                                + String.join("\n", apiDiff));
+                    //Log.debug("========================================");
+                    //Log.debug("------ PREV API ------------------------");
+                    //prevDepApi.asListOfStrings().forEach(Log::debug);
+                    //Log.debug("------ CURRENT API ---------------------");
+                    //currentDepApi.asListOfStrings().forEach(Log::debug);
+                    //Log.debug("========================================");
+                    continue nextPkg;
                 }
             }
         }
@@ -660,7 +755,6 @@
         Map<String,Transformer> suffixRules = new HashMap<>();
         suffixRules.put(".java", compileJavaPackages);
         compileJavaPackages.setExtra(args);
-
         rcValue[0] = perform(sjavac, binDir, suffixRules);
         recentlyCompiled.addAll(taintedPackages());
         clearTaintedPackages();
@@ -668,6 +762,11 @@
         taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled);
         packagesWithChangedPublicApis = new HashSet<>();
         return again && rcValue[0];
+
+        // TODO: Figure out why 'again' checks packagesWithChangedPublicAPis.
+        // (It shouldn't matter if packages had changed pub apis as long as no
+        // one depends on them. Wouldn't it make more sense to let 'again'
+        // depend on taintedPackages?)
     }
 
     /**
@@ -699,68 +798,101 @@
         Map<Transformer,Map<String,Set<URI>>> groupedSources = new HashMap<>();
         for (Source src : now.sources().values()) {
             Transformer t = suffixRules.get(src.suffix());
-               if (t != null) {
+            if (t != null) {
                 if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) {
                     addFileToTransform(groupedSources, t, src);
                 }
             }
         }
         // Go through the transforms and transform them.
-        for (Map.Entry<Transformer,Map<String,Set<URI>>> e : groupedSources.entrySet()) {
+        for (Map.Entry<Transformer, Map<String, Set<URI>>> e : groupedSources.entrySet()) {
             Transformer t = e.getKey();
-            Map<String,Set<URI>> srcs = e.getValue();
-            // These maps need to be synchronized since multiple threads will be writing results into them.
-            Map<String,Set<URI>> packageArtifacts =
-                    Collections.synchronizedMap(new HashMap<String,Set<URI>>());
-            Map<String,Set<String>> packageDependencies =
-                    Collections.synchronizedMap(new HashMap<String,Set<String>>());
-            Map<String,String> packagePublicApis =
-                    Collections.synchronizedMap(new HashMap<String, String>());
+            Map<String, Set<URI>> srcs = e.getValue();
+            // These maps need to be synchronized since multiple threads will be
+            // writing results into them.
+            Map<String, Set<URI>> packageArtifacts = Collections.synchronizedMap(new HashMap<>());
+            Map<String, Map<String, Set<String>>> packageDependencies = Collections.synchronizedMap(new HashMap<>());
+            Map<String, Map<String, Set<String>>> packageCpDependencies = Collections.synchronizedMap(new HashMap<>());
+            Map<String, PubApi> packagePublicApis = Collections.synchronizedMap(new HashMap<>());
+            Map<String, PubApi> dependencyPublicApis = Collections.synchronizedMap(new HashMap<>());
 
-            boolean  r = t.transform(sjavac,
-                                     srcs,
-                                     visibleSrcs,
-                                     visibleClasses,
-                                     prev.dependents(),
-                                     outputDir.toURI(),
-                                     packageArtifacts,
-                                     packageDependencies,
-                                     packagePublicApis,
-                                     0,
-                                     isIncremental(),
-                                     numCores,
-                                     out,
-                                     err);
-            if (!r) rc = false;
+            boolean r = t.transform(sjavac,
+                                    srcs,
+                                    visibleSrcs,
+                                    visibleClasses,
+                                    prev.dependents(),
+                                    outputDir.toURI(),
+                                    packageArtifacts,
+                                    packageDependencies,
+                                    packageCpDependencies,
+                                    packagePublicApis,
+                                    dependencyPublicApis,
+                                    0,
+                                    isIncremental(),
+                                    numCores,
+                                    out,
+                                    err);
+            if (!r)
+                rc = false;
 
             for (String p : srcs.keySet()) {
                 recompiledPackages.add(p);
             }
             // The transform is done! Extract all the artifacts and store the info into the Package objects.
-            for (Map.Entry<String,Set<URI>> a : packageArtifacts.entrySet()) {
+            for (Map.Entry<String, Set<URI>> a : packageArtifacts.entrySet()) {
                 Module mnow = now.findModuleFromPackageName(a.getKey());
                 mnow.addArtifacts(a.getKey(), a.getValue());
             }
             // Extract all the dependencies and store the info into the Package objects.
-            for (Map.Entry<String,Set<String>> a : packageDependencies.entrySet()) {
-                Set<String> deps = a.getValue();
+            for (Map.Entry<String, Map<String, Set<String>>> a : packageDependencies.entrySet()) {
+                Map<String, Set<String>> deps = a.getValue();
                 Module mnow = now.findModuleFromPackageName(a.getKey());
-                mnow.setDependencies(a.getKey(), deps);
+                mnow.setDependencies(a.getKey(), deps, false);
+            }
+            for (Map.Entry<String, Map<String, Set<String>>> a : packageCpDependencies.entrySet()) {
+                Map<String, Set<String>> deps = a.getValue();
+                Module mnow = now.findModuleFromPackageName(a.getKey());
+                mnow.setDependencies(a.getKey(), deps, true);
             }
-            // Extract all the pubapis and store the info into the Package objects.
-            for (Map.Entry<String,String> a : packagePublicApis.entrySet()) {
-                Module mprev = prev.findModuleFromPackageName(a.getKey());
-                List<String> pubapi = Package.pubapiToList(a.getValue());
-                Module mnow = now.findModuleFromPackageName(a.getKey());
-                mnow.setPubapi(a.getKey(), pubapi);
-                if (mprev.hasPubapiChanged(a.getKey(), pubapi)) {
+
+            // This map contains the public api of the types that this
+            // compilation depended upon. This means that it may not contain
+            // full packages. In other words, we shouldn't remove knowledge of
+            // public apis but merge these with what we already have.
+            for (Map.Entry<String, PubApi> a : dependencyPublicApis.entrySet()) {
+                String pkg = a.getKey();
+                PubApi packagePartialPubApi = a.getValue();
+                Package pkgNow = now.findModuleFromPackageName(pkg).lookupPackage(pkg);
+                PubApi currentPubApi = pkgNow.getPubApi();
+                PubApi newPubApi = PubApi.mergeTypes(currentPubApi, packagePartialPubApi);
+                pkgNow.setPubapi(newPubApi);
+
+                // See JDK-8071904
+                if (now.packages().containsKey(pkg))
+                    now.packages().get(pkg).setPubapi(newPubApi);
+                else
+                    now.packages().put(pkg, pkgNow);
+            }
+
+            // The packagePublicApis cover entire packages (since sjavac compiles
+            // stuff on package level). This means that if a type is missing
+            // in the public api of a given package, it means that it has been
+            // removed. In other words, we should *set* the pubapi to whatever
+            // this map contains, and not merge it with what we already have.
+            for (Map.Entry<String, PubApi> a : packagePublicApis.entrySet()) {
+                String pkg = a.getKey();
+                PubApi newPubApi = a.getValue();
+                Module mprev = prev.findModuleFromPackageName(pkg);
+                Module mnow = now.findModuleFromPackageName(pkg);
+                mnow.setPubapi(pkg, newPubApi);
+                if (mprev.hasPubapiChanged(pkg, newPubApi)) {
                     // Aha! The pubapi of this package has changed!
                     // It can also be a new compile from scratch.
-                    if (mprev.lookupPackage(a.getKey()).existsInJavacState()) {
+                    if (mprev.lookupPackage(pkg).existsInJavacState()) {
                         // This is an incremental compile! The pubapi
                         // did change. Trigger recompilation of dependents.
-                        packagesWithChangedPublicApis.add(a.getKey());
-                        Log.info("The pubapi of "+Util.justPackageName(a.getKey())+" has changed!");
+                        packagesWithChangedPublicApis.add(pkg);
+                        Log.info("The API of " + Util.justPackageName(pkg) + " has changed!");
                     }
                 }
             }
@@ -791,17 +923,21 @@
     }
 
     /**
-     * Compare the calculate source list, with an explicit list, usually supplied from the makefile.
-     * Used to detect bugs where the makefile and sjavac have different opinions on which files
-     * should be compiled.
+     * Compare the calculate source list, with an explicit list, usually
+     * supplied from the makefile. Used to detect bugs where the makefile and
+     * sjavac have different opinions on which files should be compiled.
      */
-    public void compareWithMakefileList(File makefileSourceList) throws ProblemException {
-        // If we are building on win32 using for example cygwin the paths in the makefile source list
+    public void compareWithMakefileList(File makefileSourceList)
+            throws ProblemException {
+        // If we are building on win32 using for example cygwin the paths in the
+        // makefile source list
         // might be /cygdrive/c/.... which does not match c:\....
-        // We need to adjust our calculated sources to be identical, if necessary.
+        // We need to adjust our calculated sources to be identical, if
+        // necessary.
         boolean mightNeedRewriting = File.pathSeparatorChar == ';';
 
-        if (makefileSourceList == null) return;
+        if (makefileSourceList == null)
+            return;
 
         Set<String> calculatedSources = new HashSet<>();
         Set<String> listedSources = new HashSet<>();