Skip to content

Commit 18be066

Browse files
committed
[sourcekit] Synchronize cursor-info requests on generated interfaces
Cursor info requires access to the underlying AST, which is not thread-safe. This manifest as crashes when performing concurrent cursor-info requests on the same generated interface. We already prevented concurrent cursor-infos on regular Swift files by using the ASTManager, but generated interfaces use the InterfaceGenContext which may use either an ASTUnit or its own internal CompilerInstance. rdar://problem/27311624
1 parent 8e0ff51 commit 18be066

File tree

6 files changed

+76
-18
lines changed

6 files changed

+76
-18
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foo
2+
3+
// REQUIRES: objc_interop
4+
5+
// Perform 8 concurrent cursor infos, which is often enough to cause
6+
// contention. We disable printing the requests to minimize delay.
7+
8+
// RUN: %sourcekitd-test -req=interface-gen-open -module Foo -- \
9+
// RUN: -F %S/../Inputs/libIDE-mock-sdk %mcp_opt %clang-importer-sdk \
10+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 \
11+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 \
12+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 \
13+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 \
14+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 \
15+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 \
16+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 \
17+
// RUN: == -async -dont-print-request -req=cursor -pos=60:15 | FileCheck %s -check-prefix=CHECK-FOO
18+
19+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions
20+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions
21+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions
22+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions
23+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions
24+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions
25+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions
26+
// CHECK-FOO: <decl.struct><syntaxtype.keyword>struct</syntaxtype.keyword> <decl.name>FooRuncingOptions

tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ namespace SourceKit {
209209
EditorDiagConsumer &ASTUnit::getEditorDiagConsumer() const {
210210
return Impl.CollectDiagConsumer;
211211
}
212+
213+
void ASTUnit::performAsync(std::function<void()> Fn) {
214+
Impl.Queue.dispatch(std::move(Fn));
215+
}
212216
}
213217

214218
namespace {

tools/SourceKit/lib/SwiftLang/SwiftASTManager.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "SourceKit/Core/LLVM.h"
1818
#include "llvm/ADT/ArrayRef.h"
1919
#include "llvm/ADT/StringRef.h"
20+
#include <functional>
2021
#include <string>
2122

2223
namespace llvm {
@@ -55,6 +56,10 @@ class ASTUnit : public SourceKit::ThreadSafeRefCountedBase<ASTUnit> {
5556
ArrayRef<ImmutableTextSnapshotRef> getSnapshots() const;
5657
EditorDiagConsumer &getEditorDiagConsumer() const;
5758
swift::SourceFile &getPrimarySourceFile() const;
59+
60+
/// Perform \p Fn asynchronously while preventing concurrent access to the
61+
/// AST.
62+
void performAsync(std::function<void()> Fn);
5863
};
5964

6065
typedef IntrusiveRefCntPtr<ASTUnit> ASTUnitRef;

tools/SourceKit/lib/SwiftLang/SwiftEditorInterfaceGen.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ class SwiftInterfaceGenContext::Implementation {
7979
SourceTextInfo Info;
8080
// This is the non-typechecked AST for the generated interface source.
8181
CompilerInstance TextCI;
82+
// Syncronize access to the embedded compiler instance (if we don't have an
83+
// ASTUnit).
84+
WorkQueue Queue{WorkQueue::Dequeuing::Serial,
85+
"sourcekit.swift.InterfaceGenContext"};
8286
};
8387

8488
typedef SwiftInterfaceGenContext::Implementation::TextRange TextRange;
@@ -498,6 +502,14 @@ void SwiftInterfaceGenContext::reportEditorInfo(EditorConsumer &Consumer) const
498502
Consumer.finished();
499503
}
500504

505+
void SwiftInterfaceGenContext::accessASTAsync(std::function<void()> Fn) {
506+
if (Impl.AstUnit) {
507+
Impl.AstUnit->performAsync(std::move(Fn));
508+
} else {
509+
Impl.Queue.dispatch(std::move(Fn));
510+
}
511+
}
512+
501513
SwiftInterfaceGenContext::ResolvedEntity
502514
SwiftInterfaceGenContext::resolveEntityForOffset(unsigned Offset) const {
503515
// Search among the references.
@@ -633,8 +645,10 @@ void SwiftLangSupport::editorOpenInterface(EditorConsumer &Consumer,
633645
return;
634646
}
635647

636-
IFaceGenContexts.set(Name, IFaceGenRef);
637648
IFaceGenRef->reportEditorInfo(Consumer);
649+
// reportEditorInfo requires exclusive access to the AST, so don't add this
650+
// to the service cache until it has returned.
651+
IFaceGenContexts.set(Name, IFaceGenRef);
638652
}
639653

640654
class PrimaryFileInterfaceConsumer : public SwiftASTConsumer {
@@ -740,8 +754,10 @@ void SwiftLangSupport::editorOpenHeaderInterface(EditorConsumer &Consumer,
740754
return;
741755
}
742756

743-
IFaceGenContexts.set(Name, IFaceGenRef);
744757
IFaceGenRef->reportEditorInfo(Consumer);
758+
// reportEditorInfo requires exclusive access to the AST, so don't add this
759+
// to the service cache until it has returned.
760+
IFaceGenContexts.set(Name, IFaceGenRef);
745761
}
746762

747763
void SwiftLangSupport::findInterfaceDocument(StringRef ModuleName,

tools/SourceKit/lib/SwiftLang/SwiftInterfaceGenContext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class SwiftInterfaceGenContext :
5555

5656
bool matches(StringRef ModuleName, const swift::CompilerInvocation &Invok);
5757

58+
/// Note: requires exclusive access to the underlying AST.
5859
void reportEditorInfo(EditorConsumer &Consumer) const;
5960

6061
struct ResolvedEntity {
@@ -71,8 +72,12 @@ class SwiftInterfaceGenContext :
7172
bool isResolved() const { return Dcl || Mod; }
7273
};
7374

75+
/// Provides exclusive access to the underlying AST.
76+
void accessASTAsync(std::function<void()> Fn);
77+
7478
/// Returns the resolved entity along with a boolean indicating if it is a
7579
/// reference or not.
80+
/// Note: requires exclusive access to the underlying AST. See accessASTAsync.
7681
ResolvedEntity resolveEntityForOffset(unsigned Offset) const;
7782

7883
/// Searches for a declaration with the given USR and returns the

tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,24 +1024,26 @@ void SwiftLangSupport::getCursorInfo(
10241024
SwiftArgs, OpArgs);
10251025
}
10261026

1027-
SwiftInterfaceGenContext::ResolvedEntity Entity;
1028-
Entity = IFaceGenRef->resolveEntityForOffset(Offset);
1029-
if (Entity.isResolved()) {
1030-
CompilerInvocation Invok;
1031-
IFaceGenRef->applyTo(Invok);
1032-
if (Entity.Mod) {
1033-
passCursorInfoForModule(Entity.Mod, IFaceGenContexts, Invok, Receiver);
1027+
IFaceGenRef->accessASTAsync([this, IFaceGenRef, Offset, Receiver] {
1028+
SwiftInterfaceGenContext::ResolvedEntity Entity;
1029+
Entity = IFaceGenRef->resolveEntityForOffset(Offset);
1030+
if (Entity.isResolved()) {
1031+
CompilerInvocation Invok;
1032+
IFaceGenRef->applyTo(Invok);
1033+
if (Entity.Mod) {
1034+
passCursorInfoForModule(Entity.Mod, IFaceGenContexts, Invok,
1035+
Receiver);
1036+
} else {
1037+
// FIXME: Should pass the main module for the interface but currently
1038+
// it's not necessary.
1039+
passCursorInfoForDecl(
1040+
Entity.Dcl, /*MainModule*/ nullptr, Type(), Type(), Entity.IsRef,
1041+
/*OrigBufferID=*/None, *this, Invok, {}, Receiver);
1042+
}
10341043
} else {
1035-
// FIXME: Should pass the main module for the interface but currently
1036-
// it's not necessary.
1037-
passCursorInfoForDecl(Entity.Dcl, /*MainModule*/nullptr, Type(), Type(),
1038-
Entity.IsRef,
1039-
/*OrigBufferID=*/None, *this, Invok,
1040-
{}, Receiver);
1044+
Receiver({});
10411045
}
1042-
} else {
1043-
Receiver({});
1044-
}
1046+
});
10451047
return;
10461048
}
10471049

0 commit comments

Comments
 (0)