Skip to content

Commit 2298a95

Browse files
Fix: multiple compilation units in a file lead to information loss (#738)
Fixes GRAPH-753 We discovered that javac issues multiple events per file if there are multiple top level definitions that match what javac considers to be a compilation unit - interfaces or classes for example. These multiple invocations produce slightly different results, and all writing to the same file, meaning the information gets lost. To fix this, we defensively read the file if it already exists and carefully deduplicate symbols/occurrences/synthetics before writing back.
1 parent 4a6906a commit 2298a95

File tree

7 files changed

+212
-21
lines changed

7 files changed

+212
-21
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ lazy val docs = project
545545
.enablePlugins(DocusaurusPlugin)
546546

547547
lazy val javaOnlySettings = List[Def.Setting[_]](
548+
javafmtOnCompile := false,
548549
autoScalaLibrary := false,
549550
incOptions ~= { old =>
550551
old.withEnabled(false).withApiDebug(true)

semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
import javax.lang.model.util.Types;
99

1010
import javax.tools.JavaFileObject;
11-
import java.io.ByteArrayOutputStream;
12-
import java.io.IOException;
13-
import java.io.PrintWriter;
11+
import java.io.*;
1412
import java.net.URI;
1513
import java.nio.file.Files;
1614
import java.nio.file.Path;
1715
import java.nio.file.Paths;
16+
import java.util.HashSet;
17+
import java.util.Set;
18+
import java.util.stream.Collectors;
1819

1920
/**
2021
* Callback hook that generates SemanticDB when the compiler has completed typechecking a Java
@@ -44,7 +45,22 @@ public SemanticdbTaskListener(
4445
}
4546

4647
@Override
47-
public void started(TaskEvent e) {}
48+
public void started(TaskEvent e) {
49+
// Upon first encounter with a file (before any other tasks are run)
50+
// we remove the semanticdb file for this source file to ensure
51+
// stale data doesn't cause problems
52+
if (e.getKind() == TaskEvent.Kind.ENTER) {
53+
inferBazelSourceroot(e.getSourceFile());
54+
Result<Path, String> semanticdbPath = semanticdbOutputPath(options, e);
55+
if (semanticdbPath.isOk()) {
56+
try {
57+
Files.deleteIfExists(semanticdbPath.getOrThrow());
58+
} catch (IOException ex) {
59+
this.reportException(ex, e);
60+
}
61+
}
62+
}
63+
}
4864

4965
@Override
5066
public void finished(TaskEvent e) {
@@ -78,8 +94,10 @@ public void finished(TaskEvent e) {
7894
}
7995
}
8096

81-
// Uses reporter.error with the full stack trace of the exception instead of reporter.exception
82-
// because reporter.exception doesn't seem to print any meaningful information about the
97+
// Uses reporter.error with the full stack trace of the exception instead of
98+
// reporter.exception
99+
// because reporter.exception doesn't seem to print any meaningful information
100+
// about the
83101
// exception, it just prints the location with an empty message.
84102
private void reportException(Throwable exception, TaskEvent e) {
85103
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -96,7 +114,9 @@ private void onFinishedAnalyze(TaskEvent e) {
96114
Semanticdb.TextDocument textDocument =
97115
new SemanticdbVisitor(globals, e.getCompilationUnit(), options, types, trees, elements)
98116
.buildTextDocument(e.getCompilationUnit());
99-
writeSemanticdb(e, path.getOrThrow(), textDocument);
117+
Path output = path.getOrThrow();
118+
if (Files.exists(output)) appendSemanticdb(e, output, textDocument);
119+
else writeSemanticdb(e, output, textDocument);
100120
} else {
101121
reporter.error(path.getErrorOrThrow(), e);
102122
}
@@ -114,6 +134,78 @@ private void writeSemanticdb(TaskEvent event, Path output, Semanticdb.TextDocume
114134
}
115135
}
116136

137+
private void appendSemanticdb(
138+
TaskEvent event, Path output, Semanticdb.TextDocument textDocument) {
139+
/*
140+
* If there already is a semanticdb file at the given path,
141+
* we do the following:
142+
* - Read a documents collection
143+
* - Try to find the document with the matching relative path (matching the incoming textDocument)
144+
* - Then, depending on whether a matching document already exists in the collection:
145+
* - if YES, mutate it in place to only add entries from the incoming document
146+
* - if NO, simply add the incoming text document to the collection
147+
* - Write the collection back to disk
148+
* */
149+
Semanticdb.TextDocument document = null;
150+
int documentIndex = -1;
151+
Semanticdb.TextDocuments documents = null;
152+
153+
try (InputStream is = Files.newInputStream(output.toFile().toPath())) {
154+
documents = Semanticdb.TextDocuments.parseFrom(is);
155+
156+
for (int i = 0; i < documents.getDocumentsCount(); i++) {
157+
Semanticdb.TextDocument candidate = documents.getDocuments(i);
158+
if (document == null && candidate.getUri().equals(textDocument.getUri())) {
159+
document = candidate;
160+
documentIndex = i;
161+
}
162+
}
163+
164+
} catch (IOException e) {
165+
this.reportException(e, event);
166+
return;
167+
}
168+
169+
if (document != null) {
170+
// If there is a previous semanticdb document at this path, we need
171+
// to deduplicate symbols and occurrences and mutate the document in place
172+
Set<Semanticdb.SymbolInformation> symbols = new HashSet<>(textDocument.getSymbolsList());
173+
Set<Semanticdb.SymbolOccurrence> occurrences =
174+
new HashSet<>(textDocument.getOccurrencesList());
175+
Set<Semanticdb.Synthetic> synthetics = new HashSet<>(textDocument.getSyntheticsList());
176+
177+
symbols.addAll(document.getSymbolsList());
178+
occurrences.addAll(document.getOccurrencesList());
179+
synthetics.addAll(document.getSyntheticsList());
180+
181+
documents
182+
.toBuilder()
183+
.addDocuments(
184+
documentIndex,
185+
document
186+
.toBuilder()
187+
.clearOccurrences()
188+
.addAllOccurrences(occurrences)
189+
.clearSymbols()
190+
.addAllSymbols(symbols)
191+
.clearSynthetics()
192+
.addAllSynthetics(synthetics));
193+
194+
} else {
195+
// If no prior document was found, we can just add the incoming one to the collection
196+
documents = documents.toBuilder().addDocuments(textDocument).build();
197+
}
198+
199+
byte[] bytes = documents.toByteArray();
200+
201+
try {
202+
Files.createDirectories(output.getParent());
203+
Files.write(output, bytes);
204+
} catch (IOException e) {
205+
this.reportException(e, event);
206+
}
207+
}
208+
117209
public static Path absolutePathFromUri(SemanticdbJavacOptions options, JavaFileObject file) {
118210
URI uri = file.toUri();
119211
if ((options.uriScheme == UriScheme.SBT || options.uriScheme == UriScheme.ZINC)
@@ -210,10 +302,13 @@ private Result<Path, String> semanticdbOutputPath(SemanticdbJavacOptions options
210302

211303
switch (options.noRelativePath) {
212304
case INDEX_ANYWAY:
213-
// Come up with a unique relative path for this file even if it's not under the sourceroot.
214-
// By indexing auto-generated files, we collect SymbolInformation for auto-generated symbol,
305+
// Come up with a unique relative path for this file even if it's not under the
306+
// sourceroot.
307+
// By indexing auto-generated files, we collect SymbolInformation for
308+
// auto-generated symbol,
215309
// which results in more useful hover tooltips in the editor.
216-
// In the future, we may want to additionally embed the full text contents of these files
310+
// In the future, we may want to additionally embed the full text contents of
311+
// these files
217312
// so that it's possible to browse generated files with precise code navigation.
218313
String uniqueFilename =
219314
String.format("%d.%s.semanticdb", ++noRelativePathCounter, absolutePath.getFileName());

tests/minimized/src/main/java/minimized/Interfaces.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,15 @@ default String defaultInterfaceMethod() {
99
return "default";
1010
}
1111
}
12+
13+
interface BookService {
14+
void checkPages();
15+
}
16+
17+
interface MyService {
18+
BookService bookService();
19+
20+
default void example() {
21+
bookService().checkPages();
22+
}
23+
}

tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/AnnotationParameters.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
// kind Interface
88
// relationship is_implementation semanticdb maven jdk 11 java/lang/annotation/Annotation#
99
double value();
10+
// ^^^^^ definition semanticdb maven . . minimized/Bar#value().
11+
// display_name value
12+
// signature_documentation java public abstract double value()
13+
// kind AbstractMethod
1014
}
1115

1216
@interface BarB {
@@ -16,6 +20,10 @@
1620
// kind Interface
1721
// relationship is_implementation semanticdb maven jdk 11 java/lang/annotation/Annotation#
1822
boolean value();
23+
// ^^^^^ definition semanticdb maven . . minimized/BarB#value().
24+
// display_name value
25+
// signature_documentation java public abstract boolean value()
26+
// kind AbstractMethod
1927
}
2028

2129
@interface Nullable {
@@ -25,6 +33,11 @@
2533
// kind Interface
2634
// relationship is_implementation semanticdb maven jdk 11 java/lang/annotation/Annotation#
2735
String value() default "";
36+
//^^^^^ reference semanticdb maven jdk 11 java/lang/String#
37+
// ^^^^^ definition semanticdb maven . . minimized/Nullable#value().
38+
// display_name value
39+
// signature_documentation java public abstract String value()
40+
// kind AbstractMethod
2841
}
2942

3043
interface Foo {

tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/AnnotationsOnParameterizedTypes.java

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,44 @@ public interface AnnotationsOnParameterizedTypes {
3333
// kind Interface
3434

3535
public static AnnotationsOnParameterizedTypes getInstance() {
36+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#
37+
// ^^^^^^^^^^^ definition semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#getInstance().
38+
// display_name getInstance
39+
// signature_documentation java public static AnnotationsOnParameterizedTypes getInstance()
40+
// kind StaticMethod
3641
return new AnnotationsOnParameterizedTypesImpl();
42+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#`<init>`().
3743
}
3844

3945
<C, W> Function<W, C> adapter(Class<C> contract, Class<W> wrappedClass);
46+
// ^ definition semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().[C]
47+
// display_name C
48+
// signature_documentation java C
49+
// kind TypeParameter
50+
// ^ definition semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().[W]
51+
// display_name W
52+
// signature_documentation java W
53+
// kind TypeParameter
54+
// ^^^^^^^^ reference semanticdb maven jdk 11 java/util/function/Function#
55+
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().[W]
56+
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().[C]
57+
// ^^^^^^^ definition semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().
58+
// display_name adapter
59+
// signature_documentation java public abstract <C, W> Function<W, C> adapter(Class<C> contract, Class<W> wrappedClass)
60+
// kind AbstractMethod
61+
// relationship is_reference is_implementation semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#adapter().
62+
// ^^^^^ reference semanticdb maven jdk 11 java/lang/Class#
63+
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().[C]
64+
// ^^^^^^^^ definition local 0
65+
// display_name contract
66+
// signature_documentation java Class<C> contract
67+
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().
68+
// ^^^^^ reference semanticdb maven jdk 11 java/lang/Class#
69+
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().[W]
70+
// ^^^^^^^^^^^^ definition local 1
71+
// display_name wrappedClass
72+
// signature_documentation java Class<W> wrappedClass
73+
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().
4074
}
4175

4276

@@ -82,13 +116,13 @@ public <C, W> Function<W, C> adapter(Class<C> contract, Class<W> wrappedClass) {
82116
// relationship is_reference is_implementation semanticdb maven . . minimized/AnnotationsOnParameterizedTypes#adapter().
83117
// ^^^^^ reference semanticdb maven jdk 11 java/lang/Class#
84118
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#adapter().[C]
85-
// ^^^^^^^^ definition local 0
119+
// ^^^^^^^^ definition local 2
86120
// display_name contract
87121
// signature_documentation java Class<C> contract
88122
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#adapter().
89123
// ^^^^^ reference semanticdb maven jdk 11 java/lang/Class#
90124
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#adapter().[W]
91-
// ^^^^^^^^^^^^ definition local 1
125+
// ^^^^^^^^^^^^ definition local 3
92126
// display_name wrappedClass
93127
// signature_documentation java Class<W> wrappedClass
94128
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#adapter().
@@ -97,19 +131,19 @@ public <C, W> Function<W, C> adapter(Class<C> contract, Class<W> wrappedClass) {
97131
// ^^^^^^^^ reference semanticdb maven jdk 11 java/util/function/Function#
98132
// ^^^^^^^^^^^^^^^^^ reference semanticdb maven jdk 11 java/lang/reflect/InvocationHandler#
99133
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#adapter().[C]
100-
// ^^^^^^^^^^^ definition local 2
134+
// ^^^^^^^^^^^ definition local 4
101135
// display_name constructor
102136
// signature_documentation java Function<InvocationHandler, C> constructor
103137
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#adapter().
104138
// kind Variable
105139
// ^^^^^^^^^^^^^^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().
106-
// ^^^^^^^^ reference local 0
140+
// ^^^^^^^^ reference local 2
107141

108142
System.out.println(constructor);
109143
// ^^^^^^ reference semanticdb maven jdk 11 java/lang/System#
110144
// ^^^ reference semanticdb maven jdk 11 java/lang/System#out.
111145
// ^^^^^^^ reference semanticdb maven jdk 11 java/io/PrintStream#println(+9).
112-
// ^^^^^^^^^^^ reference local 2
146+
// ^^^^^^^^^^^ reference local 4
113147

114148
return null;
115149
}
@@ -128,7 +162,7 @@ private <T> Function<InvocationHandler, T> getConstructor(Class<T> contract) {
128162
// kind Method
129163
// ^^^^^ reference semanticdb maven jdk 11 java/lang/Class#
130164
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().[T]
131-
// ^^^^^^^^ definition local 3
165+
// ^^^^^^^^ definition local 5
132166
// display_name contract
133167
// signature_documentation java Class<T> contract
134168
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().
@@ -137,17 +171,17 @@ private <T> Function<InvocationHandler, T> getConstructor(Class<T> contract) {
137171
Constructor<T> constructor = (Constructor<T>) proxyConstructors.computeIfAbsent(contract, c -> {
138172
// ^^^^^^^^^^^ reference semanticdb maven jdk 11 java/lang/reflect/Constructor#
139173
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().[T]
140-
// ^^^^^^^^^^^ definition local 4
174+
// ^^^^^^^^^^^ definition local 6
141175
// display_name constructor
142-
// signature_documentation java @SuppressWarnings("unchecked")\nConstructor<T> constructor
176+
// signature_documentation java @SuppressWarnings\nConstructor<T> constructor
143177
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().
144178
// kind Variable
145179
// ^^^^^^^^^^^ reference semanticdb maven jdk 11 java/lang/reflect/Constructor#
146180
// ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().[T]
147181
// ^^^^^^^^^^^^^^^^^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#proxyConstructors.
148182
// ^^^^^^^^^^^^^^^ reference semanticdb maven jdk 11 java/util/concurrent/ConcurrentMap#computeIfAbsent().
149-
// ^^^^^^^^ reference local 3
150-
// ^ definition local 5
183+
// ^^^^^^^^ reference local 5
184+
// ^ definition local 7
151185
// display_name c
152186
// signature_documentation java Class<?> c
153187
// enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().
@@ -158,7 +192,7 @@ private <T> Function<InvocationHandler, T> getConstructor(Class<T> contract) {
158192
// ^^^^^^ reference semanticdb maven jdk 11 java/lang/System#
159193
// ^^^ reference semanticdb maven jdk 11 java/lang/System#out.
160194
// ^^^^^^^ reference semanticdb maven jdk 11 java/io/PrintStream#println(+9).
161-
// ^^^^^^^^^^^ reference local 4
195+
// ^^^^^^^^^^^ reference local 6
162196

163197
return null;
164198
}

0 commit comments

Comments
 (0)