Skip to content

Commit 6ea84f6

Browse files
committed
Merge remote-tracking branch 'origin/master' into master-next
2 parents 6a3bded + 0b795c1 commit 6ea84f6

File tree

3 files changed

+179
-36
lines changed

3 files changed

+179
-36
lines changed

tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,14 @@ class ASTProducer : public ThreadSafeRefCountedBase<ASTProducer> {
263263
SmallVector<BufferStamp, 8> Stamps;
264264
ThreadSafeRefCntPtr<ASTUnit> AST;
265265
SmallVector<std::pair<std::string, BufferStamp>, 8> DependencyStamps;
266-
std::vector<std::pair<SwiftASTConsumerRef, const void*>> QueuedConsumers;
266+
267+
struct QueuedConsumer {
268+
SwiftASTConsumerRef consumer;
269+
std::vector<ImmutableTextSnapshotRef> snapshots;
270+
const void *oncePerASTToken;
271+
};
272+
273+
std::vector<QueuedConsumer> QueuedConsumers;
267274
llvm::sys::Mutex Mtx;
268275

269276
public:
@@ -282,8 +289,13 @@ class ASTProducer : public ThreadSafeRefCountedBase<ASTProducer> {
282289
bool shouldRebuild(SwiftASTManager::Implementation &MgrImpl,
283290
ArrayRef<ImmutableTextSnapshotRef> Snapshots);
284291

285-
void enqueueConsumer(SwiftASTConsumerRef Consumer, const void *OncePerASTToken);
286-
std::vector<SwiftASTConsumerRef> popQueuedConsumers();
292+
void enqueueConsumer(SwiftASTConsumerRef Consumer,
293+
ArrayRef<ImmutableTextSnapshotRef> Snapshots,
294+
const void *OncePerASTToken);
295+
296+
using ConsumerPredicate = llvm::function_ref<bool(
297+
SwiftASTConsumer *, ArrayRef<ImmutableTextSnapshotRef>)>;
298+
std::vector<SwiftASTConsumerRef> takeConsumers(ConsumerPredicate predicate);
287299

288300
size_t getMemoryCost() const {
289301
// FIXME: Report the memory cost of the overall CompilerInstance.
@@ -557,19 +569,27 @@ void SwiftASTManager::processASTAsync(SwiftInvocationRef InvokRef,
557569
}
558570
}
559571

560-
Producer->enqueueConsumer(std::move(ASTConsumer), OncePerASTToken);
572+
Producer->enqueueConsumer(ASTConsumer, Snapshots, OncePerASTToken);
561573

562-
Producer->getASTUnitAsync(Impl, Snapshots,
563-
[Producer](ASTUnitRef Unit, StringRef Error) {
564-
auto Consumers = Producer->popQueuedConsumers();
574+
auto handleAST = [this, Producer, ASTConsumer](ASTUnitRef unit,
575+
StringRef error) {
576+
auto consumers = Producer->takeConsumers(
577+
[&](SwiftASTConsumer *consumer,
578+
ArrayRef<ImmutableTextSnapshotRef> snapshots) {
579+
return consumer == ASTConsumer.get() ||
580+
!Producer->shouldRebuild(Impl, snapshots) ||
581+
(unit && consumer->canUseASTWithSnapshots(snapshots));
582+
});
565583

566-
for (auto &Consumer : Consumers) {
567-
if (Unit)
568-
Unit->Impl.consumeAsync(std::move(Consumer), Unit);
569-
else
570-
Consumer->failed(Error);
571-
}
572-
});
584+
for (auto &consumer : consumers) {
585+
if (unit)
586+
unit->Impl.consumeAsync(std::move(consumer), unit);
587+
else
588+
consumer->failed(error);
589+
}
590+
};
591+
592+
Producer->getASTUnitAsync(Impl, Snapshots, std::move(handleAST));
573593
}
574594

575595
void SwiftASTManager::removeCachedAST(SwiftInvocationRef Invok) {
@@ -689,30 +709,37 @@ ASTUnitRef ASTProducer::getASTUnitImpl(SwiftASTManager::Implementation &MgrImpl,
689709
return AST;
690710
}
691711

692-
void ASTProducer::enqueueConsumer(SwiftASTConsumerRef Consumer,
693-
const void *OncePerASTToken) {
712+
void ASTProducer::enqueueConsumer(SwiftASTConsumerRef consumer,
713+
ArrayRef<ImmutableTextSnapshotRef> snapshots,
714+
const void *oncePerASTToken) {
694715
llvm::sys::ScopedLock L(Mtx);
695-
if (OncePerASTToken) {
716+
if (oncePerASTToken) {
696717
for (auto I = QueuedConsumers.begin(),
697718
E = QueuedConsumers.end(); I != E; ++I) {
698-
if (I->second == OncePerASTToken) {
699-
I->first->cancelled();
719+
if (I->oncePerASTToken == oncePerASTToken) {
720+
I->consumer->cancelled();
700721
QueuedConsumers.erase(I);
701722
break;
702723
}
703724
}
704725
}
705-
QueuedConsumers.push_back({ std::move(Consumer), OncePerASTToken });
726+
QueuedConsumers.push_back({std::move(consumer), snapshots, oncePerASTToken});
706727
}
707728

708-
std::vector<SwiftASTConsumerRef> ASTProducer::popQueuedConsumers() {
729+
std::vector<SwiftASTConsumerRef>
730+
ASTProducer::takeConsumers(ConsumerPredicate predicate) {
709731
llvm::sys::ScopedLock L(Mtx);
710-
std::vector<SwiftASTConsumerRef> Consumers;
711-
Consumers.reserve(QueuedConsumers.size());
712-
for (auto &C : QueuedConsumers)
713-
Consumers.push_back(std::move(C.first));
714-
QueuedConsumers.clear();
715-
return Consumers;
732+
std::vector<SwiftASTConsumerRef> consumers;
733+
734+
QueuedConsumers.erase(std::remove_if(QueuedConsumers.begin(),
735+
QueuedConsumers.end(), [&](QueuedConsumer &qc) {
736+
if (predicate(qc.consumer.get(), qc.snapshots)) {
737+
consumers.push_back(std::move(qc.consumer));
738+
return true;
739+
}
740+
return false;
741+
}), QueuedConsumers.end());
742+
return consumers;
716743
}
717744

718745
bool ASTProducer::shouldRebuild(SwiftASTManager::Implementation &MgrImpl,

unittests/SourceKit/SwiftLang/CursorInfoTest.cpp

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static StringRef getRuntimeLibPath() {
3030
namespace {
3131

3232
class NullEditorConsumer : public EditorConsumer {
33-
bool needsSemanticInfo() override { return false; }
33+
bool needsSemanticInfo() override { return needsSema; }
3434

3535
void handleRequestError(const char *Description) override {
3636
llvm_unreachable("unexpected error");
@@ -90,6 +90,9 @@ class NullEditorConsumer : public EditorConsumer {
9090
bool handleSourceText(StringRef Text) override { return false; }
9191
bool handleSerializedSyntaxTree(StringRef Text) override { return false; }
9292
bool syntaxTreeEnabled() override { return false; }
93+
94+
public:
95+
bool needsSema = false;
9396
};
9497

9598
struct TestCursorInfo {
@@ -100,8 +103,9 @@ struct TestCursorInfo {
100103
};
101104

102105
class CursorInfoTest : public ::testing::Test {
103-
SourceKit::Context Ctx{ getRuntimeLibPath(), SourceKit::createSwiftLangSupport };
106+
SourceKit::Context &Ctx;
104107
std::atomic<int> NumTasks;
108+
NullEditorConsumer Consumer;
105109

106110
public:
107111
LangSupport &getLang() { return Ctx.getSwiftLangSupport(); }
@@ -114,20 +118,30 @@ class CursorInfoTest : public ::testing::Test {
114118
NumTasks = 0;
115119
}
116120

121+
CursorInfoTest()
122+
: Ctx(*new SourceKit::Context(getRuntimeLibPath(),
123+
SourceKit::createSwiftLangSupport,
124+
/*dispatchOnMain=*/false)) {
125+
// This is avoiding destroying \p SourceKit::Context because another
126+
// thread may be active trying to use it to post notifications.
127+
// FIXME: Use shared_ptr ownership to avoid such issues.
128+
}
129+
117130
void addNotificationReceiver(DocumentUpdateNotificationReceiver Receiver) {
118131
Ctx.getNotificationCenter().addDocumentUpdateNotificationReceiver(Receiver);
119132
}
120133

121-
void open(StringRef DocName, StringRef Text) {
122-
NullEditorConsumer Consumer;
134+
void open(const char *DocName, StringRef Text,
135+
Optional<ArrayRef<const char *>> CArgs = llvm::None) {
136+
auto Args = CArgs.hasValue() ? makeArgs(DocName, *CArgs)
137+
: std::vector<const char *>{};
123138
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
124-
getLang().editorOpen(DocName, Buf.get(), /*EnableSyntaxMap=*/false, Consumer,
125-
/*Args=*/{});
139+
getLang().editorOpen(DocName, Buf.get(), /*EnableSyntaxMap=*/false,
140+
Consumer, Args);
126141
}
127142

128143
void replaceText(StringRef DocName, unsigned Offset, unsigned Length,
129144
StringRef Text) {
130-
NullEditorConsumer Consumer;
131145
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
132146
getLang().editorReplaceText(DocName, Buf.get(), Offset, Length, Consumer);
133147
}
@@ -159,6 +173,8 @@ class CursorInfoTest : public ::testing::Test {
159173
return pos;
160174
}
161175

176+
void setNeedsSema(bool needsSema) { Consumer.needsSema = needsSema; }
177+
162178
private:
163179
std::vector<const char *> makeArgs(const char *DocName,
164180
ArrayRef<const char *> CArgs) {
@@ -348,3 +364,32 @@ TEST_F(CursorInfoTest, CursorInfoMustWaitDueToken) {
348364
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
349365
EXPECT_EQ(strlen("fog"), Info.DeclarationLoc->second);
350366
}
367+
368+
TEST_F(CursorInfoTest, CursorInfoMustWaitDueTokenRace) {
369+
const char *DocName = "/test.swift";
370+
const char *Contents = "let value = foo\n"
371+
"let foo = 0\n";
372+
const char *Args[] = {"-parse-as-library"};
373+
374+
auto FooRefOffs = findOffset("foo", Contents);
375+
auto FooOffs = findOffset("foo =", Contents);
376+
377+
// Open with args, kicking off an ast build. The hope of this tests is for
378+
// this AST to still be in the process of building when we start the cursor
379+
// info, to ensure the ASTManager doesn't try to handle this cursor info with
380+
// the wrong AST.
381+
setNeedsSema(true);
382+
open(DocName, Contents, llvm::makeArrayRef(Args));
383+
// Change 'foo' to 'fog' by replacing the last character.
384+
replaceText(DocName, FooRefOffs + 2, 1, "g");
385+
replaceText(DocName, FooOffs + 2, 1, "g");
386+
387+
// Should wait for the new AST, because the cursor location points to a
388+
// different token.
389+
auto Info = getCursor(DocName, FooRefOffs, Args);
390+
EXPECT_STREQ("fog", Info.Name.c_str());
391+
EXPECT_STREQ("Int", Info.Typename.c_str());
392+
ASSERT_TRUE(Info.DeclarationLoc.hasValue());
393+
EXPECT_EQ(FooOffs, Info.DeclarationLoc->first);
394+
EXPECT_EQ(strlen("fog"), Info.DeclarationLoc->second);
395+
}

unittests/SourceKit/SwiftLang/EditingTest.cpp

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,15 @@ class EditTest : public ::testing::Test {
142142
Ctx->getNotificationCenter().addDocumentUpdateNotificationReceiver(Receiver);
143143
}
144144

145-
bool waitForDocUpdate() {
145+
bool waitForDocUpdate(bool reset = false) {
146146
std::chrono::seconds secondsToWait(10);
147147
std::unique_lock<std::mutex> lk(DocUpdState->Mtx);
148148
auto when = std::chrono::system_clock::now() + secondsToWait;
149-
return !DocUpdState->CV.wait_until(
149+
auto result = !DocUpdState->CV.wait_until(
150150
lk, when, [&]() { return DocUpdState->HasUpdate; });
151+
if (reset)
152+
DocUpdState->HasUpdate = false;
153+
return result;
151154
}
152155

153156
void open(const char *DocName, StringRef Text, ArrayRef<const char *> CArgs,
@@ -158,6 +161,10 @@ class EditTest : public ::testing::Test {
158161
Args);
159162
}
160163

164+
void close(const char *DocName) {
165+
getLang().editorClose(DocName, /*removeCache=*/false);
166+
}
167+
161168
void replaceText(StringRef DocName, unsigned Offset, unsigned Length,
162169
StringRef Text, EditorConsumer &Consumer) {
163170
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
@@ -177,6 +184,8 @@ class EditTest : public ::testing::Test {
177184
DocUpdState->HasUpdate = false;
178185
}
179186

187+
void doubleOpenWithDelay(useconds_t delay, bool close);
188+
180189
private:
181190
std::vector<const char *> makeArgs(const char *DocName,
182191
ArrayRef<const char *> CArgs) {
@@ -226,3 +235,65 @@ TEST_F(EditTest, DiagsAfterEdit) {
226235
}
227236
EXPECT_EQ(SemaDiagStage, Consumer.DiagStage);
228237
}
238+
239+
void EditTest::doubleOpenWithDelay(useconds_t delay, bool closeDoc) {
240+
const char *DocName = "/test.swift";
241+
const char *Contents =
242+
"func foo() { _ = unknown_name }\n";
243+
const char *Args[] = { "-parse-as-library" };
244+
245+
DiagConsumer Consumer;
246+
open(DocName, Contents, Args, Consumer);
247+
ASSERT_EQ(0u, Consumer.Diags.size());
248+
// Open again without closing; this reinitializes the semantic info on the doc
249+
if (delay)
250+
usleep(delay);
251+
if (closeDoc)
252+
close(DocName);
253+
reset(Consumer);
254+
open(DocName, Contents, Args, Consumer);
255+
ASSERT_EQ(0u, Consumer.Diags.size());
256+
257+
// Wait for the document update from the second time we open the document. We
258+
// may or may not get a notification from the first time it was opened, but
259+
// only the second time will there be any semantic information available to
260+
// be queried, since the semantic info from the first open is unreachable.
261+
for (int i = 0; i < 2; ++i) {
262+
bool expired = waitForDocUpdate(/*reset=*/true);
263+
ASSERT_FALSE(expired) << "no second notification";
264+
replaceText(DocName, 0, 0, StringRef(), Consumer);
265+
if (!Consumer.Diags.empty())
266+
break;
267+
ASSERT_EQ(0, i) << "no diagnostics after second notification";
268+
}
269+
270+
ASSERT_EQ(1u, Consumer.Diags.size());
271+
EXPECT_STREQ("use of unresolved identifier 'unknown_name'", Consumer.Diags[0].Description.c_str());
272+
}
273+
274+
TEST_F(EditTest, DiagsAfterCloseAndReopen) {
275+
// Attempt to open the same file twice in a row. This tests (subject to
276+
// timing) cases where:
277+
// * the 2nd open happens before the first AST starts building
278+
// * the 2nd open happens after the first AST starts building
279+
// * the 2nd open happens after the AST finishes
280+
281+
// The middle case in particular verifies the ASTManager is only calling the
282+
// correct ASTConsumers.
283+
284+
doubleOpenWithDelay(0, true);
285+
doubleOpenWithDelay(1000, true); // 1 ms
286+
doubleOpenWithDelay(10000, true); // 10 ms
287+
doubleOpenWithDelay(100000, true); // 100 ms
288+
}
289+
290+
TEST_F(EditTest, DiagsAfterReopen) {
291+
// See description of DiagsAfterCloseAndReopen, but in this case we don't
292+
// close the original document, causing it to reinitialize instead of create
293+
// a fresh document.
294+
295+
doubleOpenWithDelay(0, false);
296+
doubleOpenWithDelay(1000, false); // 1 ms
297+
doubleOpenWithDelay(10000, false); // 10 ms
298+
doubleOpenWithDelay(100000, false); // 100 ms
299+
}

0 commit comments

Comments
 (0)