-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: multiple compilation units in a file lead to information loss #738
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c055917
investigation
antonsviridov-src c02e4d4
WIP: fix by appending, not overwriting
antonsviridov-src bf6995e
Clean up semanticdb file upon entering a particular source file
antonsviridov-src 7a6b655
Make document appender more robust
antonsviridov-src 82df232
Rerun snapshots
antonsviridov-src 956383a
cleanup
antonsviridov-src 94e8cb8
Protect against NPE in path absolutisation
antonsviridov-src 69699b6
Call inferBazelSourceroot defensively
antonsviridov-src 80b5f0c
Separate writing and appending to Semanticdb
antonsviridov-src File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,13 +8,14 @@ | |
import javax.lang.model.util.Types; | ||
|
||
import javax.tools.JavaFileObject; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
import java.io.*; | ||
import java.net.URI; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* Callback hook that generates SemanticDB when the compiler has completed typechecking a Java | ||
|
@@ -44,7 +45,22 @@ public SemanticdbTaskListener( | |
} | ||
|
||
@Override | ||
public void started(TaskEvent e) {} | ||
public void started(TaskEvent e) { | ||
// Upon first encounter with a file (before any other tasks are run) | ||
// we remove the semanticdb file for this source file to ensure | ||
// stale data doesn't cause problems | ||
Comment on lines
+49
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we read previous state, we shouldn't expect the folder to be clean – without doing so I was getting very confusing results when running snapshots. |
||
if (e.getKind() == TaskEvent.Kind.ENTER) { | ||
inferBazelSourceroot(e.getSourceFile()); | ||
Result<Path, String> semanticdbPath = semanticdbOutputPath(options, e); | ||
if (semanticdbPath.isOk()) { | ||
try { | ||
Files.deleteIfExists(semanticdbPath.getOrThrow()); | ||
} catch (IOException ex) { | ||
this.reportException(ex, e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void finished(TaskEvent e) { | ||
|
@@ -78,8 +94,10 @@ public void finished(TaskEvent e) { | |
} | ||
} | ||
|
||
// Uses reporter.error with the full stack trace of the exception instead of reporter.exception | ||
// because reporter.exception doesn't seem to print any meaningful information about the | ||
// Uses reporter.error with the full stack trace of the exception instead of | ||
// reporter.exception | ||
// because reporter.exception doesn't seem to print any meaningful information | ||
// about the | ||
// exception, it just prints the location with an empty message. | ||
private void reportException(Throwable exception, TaskEvent e) { | ||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
|
@@ -96,7 +114,9 @@ private void onFinishedAnalyze(TaskEvent e) { | |
Semanticdb.TextDocument textDocument = | ||
new SemanticdbVisitor(globals, e.getCompilationUnit(), options, types, trees, elements) | ||
.buildTextDocument(e.getCompilationUnit()); | ||
writeSemanticdb(e, path.getOrThrow(), textDocument); | ||
Path output = path.getOrThrow(); | ||
if (Files.exists(output)) appendSemanticdb(e, output, textDocument); | ||
else writeSemanticdb(e, output, textDocument); | ||
} else { | ||
reporter.error(path.getErrorOrThrow(), e); | ||
} | ||
|
@@ -114,6 +134,78 @@ private void writeSemanticdb(TaskEvent event, Path output, Semanticdb.TextDocume | |
} | ||
} | ||
|
||
private void appendSemanticdb( | ||
TaskEvent event, Path output, Semanticdb.TextDocument textDocument) { | ||
/* | ||
* If there already is a semanticdb file at the given path, | ||
* we do the following: | ||
* - Read a documents collection | ||
* - Try to find the document with the matching relative path (matching the incoming textDocument) | ||
* - Then, depending on whether a matching document already exists in the collection: | ||
* - if YES, mutate it in place to only add entries from the incoming document | ||
* - if NO, simply add the incoming text document to the collection | ||
* - Write the collection back to disk | ||
* */ | ||
Semanticdb.TextDocument document = null; | ||
int documentIndex = -1; | ||
Semanticdb.TextDocuments documents = null; | ||
|
||
try (InputStream is = Files.newInputStream(output.toFile().toPath())) { | ||
documents = Semanticdb.TextDocuments.parseFrom(is); | ||
|
||
for (int i = 0; i < documents.getDocumentsCount(); i++) { | ||
Semanticdb.TextDocument candidate = documents.getDocuments(i); | ||
if (document == null && candidate.getUri().equals(textDocument.getUri())) { | ||
document = candidate; | ||
documentIndex = i; | ||
} | ||
} | ||
|
||
} catch (IOException e) { | ||
this.reportException(e, event); | ||
return; | ||
} | ||
|
||
if (document != null) { | ||
// If there is a previous semanticdb document at this path, we need | ||
// to deduplicate symbols and occurrences and mutate the document in place | ||
Set<Semanticdb.SymbolInformation> symbols = new HashSet<>(textDocument.getSymbolsList()); | ||
Set<Semanticdb.SymbolOccurrence> occurrences = | ||
new HashSet<>(textDocument.getOccurrencesList()); | ||
Set<Semanticdb.Synthetic> synthetics = new HashSet<>(textDocument.getSyntheticsList()); | ||
|
||
symbols.addAll(document.getSymbolsList()); | ||
occurrences.addAll(document.getOccurrencesList()); | ||
synthetics.addAll(document.getSyntheticsList()); | ||
|
||
documents | ||
.toBuilder() | ||
.addDocuments( | ||
documentIndex, | ||
document | ||
.toBuilder() | ||
.clearOccurrences() | ||
.addAllOccurrences(occurrences) | ||
.clearSymbols() | ||
.addAllSymbols(symbols) | ||
.clearSynthetics() | ||
.addAllSynthetics(synthetics)); | ||
|
||
} else { | ||
// If no prior document was found, we can just add the incoming one to the collection | ||
documents = documents.toBuilder().addDocuments(textDocument).build(); | ||
} | ||
|
||
byte[] bytes = documents.toByteArray(); | ||
|
||
try { | ||
Files.createDirectories(output.getParent()); | ||
Files.write(output, bytes); | ||
} catch (IOException e) { | ||
this.reportException(e, event); | ||
} | ||
} | ||
|
||
public static Path absolutePathFromUri(SemanticdbJavacOptions options, JavaFileObject file) { | ||
URI uri = file.toUri(); | ||
if ((options.uriScheme == UriScheme.SBT || options.uriScheme == UriScheme.ZINC) | ||
|
@@ -210,10 +302,13 @@ private Result<Path, String> semanticdbOutputPath(SemanticdbJavacOptions options | |
|
||
switch (options.noRelativePath) { | ||
case INDEX_ANYWAY: | ||
// Come up with a unique relative path for this file even if it's not under the sourceroot. | ||
// By indexing auto-generated files, we collect SymbolInformation for auto-generated symbol, | ||
// Come up with a unique relative path for this file even if it's not under the | ||
// sourceroot. | ||
// By indexing auto-generated files, we collect SymbolInformation for | ||
// auto-generated symbol, | ||
// which results in more useful hover tooltips in the editor. | ||
// In the future, we may want to additionally embed the full text contents of these files | ||
// In the future, we may want to additionally embed the full text contents of | ||
// these files | ||
// so that it's possible to browse generated files with precise code navigation. | ||
String uniqueFilename = | ||
String.format("%d.%s.semanticdb", ++noRelativePathCounter, absolutePath.getFileName()); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was doing my head in – IntelliJ was formatting one way, running SBT session (~snapshots/run) another way, and IJ constantly producing a warning to load filesystem changes.
Formatting on compile is evil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.