Skip to content

Commit 9f18a44

Browse files
committed
[RemoteMirror] Fix demangle node corruption caused by excessively eager clearing of the NodeFactory.
We clear the NodeFactory to prevent unbounded buildup of allocated memory, but this is done too eagerly. In particular, normalizeReflectionName can end up clearing the factory while the calling code is still using nodes that were allocated from it. To keep peak memory usage low while avoiding this problem, we introduce a checkpoint mechanism in NodeFactory. A checkpoint can be pushed and then subsequently popped. When a checkpoint is popped, only the nodes allocated since the checkpoint was pushed are invalidated and the memory reclaimed. This allows us to quickly clear short-lived nodes like those created in normalizeReflectionName, while preserving longer-lived nodes used in code calling it. Uses of clearNodeFactory are replaced with this checkpoint mechanism. rdar://106547092
1 parent d9c8dca commit 9f18a44

File tree

4 files changed

+134
-15
lines changed

4 files changed

+134
-15
lines changed

include/swift/Demangling/Demangler.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ class NodeFactory {
5353
/// The head of the single-linked slab list.
5454
Slab *CurrentSlab = nullptr;
5555

56-
/// The size of the previously allocated slab.
56+
/// The size of the previously allocated slab. This may NOT be the size of
57+
/// CurrentSlab, in the case where a checkpoint has been popped.
5758
///
5859
/// The slab size can only grow, even clear() does not reset the slab size.
5960
/// This initial size is good enough to fit most de-manglings.
@@ -220,6 +221,26 @@ class NodeFactory {
220221
return {copiedString, str.size()};
221222
}
222223

224+
/// A checkpoint which captures the allocator's state at any given time. A
225+
/// checkpoint can be popped to free all allocations made since it was made.
226+
struct Checkpoint {
227+
Slab *Slab;
228+
char *CurPtr;
229+
char *End;
230+
};
231+
232+
/// Create a new checkpoint with the current state of the allocator.
233+
Checkpoint pushCheckpoint() const;
234+
235+
/// Clear all allocations made since the given checkpoint. It is
236+
/// undefined behavior to pop checkpoints in an order other than the
237+
/// order in which they were pushed, or to pop a checkpoint when
238+
/// clear() was called after creating it. The implementation attempts
239+
/// to raise a fatal error in that case, but does not guarantee it. It
240+
/// is allowed to pop outer checkpoints without popping inner ones, or
241+
/// to call clear() without popping existing checkpoints.
242+
void popCheckpoint(Checkpoint checkpoint);
243+
223244
/// Creates a node of kind \p K.
224245
NodePointer createNode(Node::Kind K);
225246

include/swift/RemoteInspection/TypeRefBuilder.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,13 @@ class TypeRefBuilder {
456456

457457
Demangle::NodeFactory &getNodeFactory() { return Dem; }
458458

459-
void clearNodeFactory() { Dem.clear(); }
459+
NodeFactory::Checkpoint pushNodeFactoryCheckpoint() const {
460+
return Dem.pushCheckpoint();
461+
}
462+
463+
void popNodeFactoryCheckpoint(NodeFactory::Checkpoint checkpoint) {
464+
Dem.popCheckpoint(checkpoint);
465+
}
460466

461467
BuiltType decodeMangledType(Node *node, bool forRequirement = true);
462468

@@ -1190,13 +1196,14 @@ class TypeRefBuilder {
11901196
for (auto descriptor : sections.AssociatedType) {
11911197
// Read out the relevant info from the associated type descriptor:
11921198
// The type's name and which protocol conformance it corresponds to
1199+
auto checkpoint = pushNodeFactoryCheckpoint();
11931200
auto typeRef = readTypeRef(descriptor, descriptor->ConformingTypeName);
11941201
auto typeName = nodeToString(demangleTypeRef(typeRef));
11951202
auto optionalMangledTypeName = normalizeReflectionName(typeRef);
11961203
auto protocolNode = demangleTypeRef(
11971204
readTypeRef(descriptor, descriptor->ProtocolTypeName));
11981205
auto protocolName = nodeToString(protocolNode);
1199-
clearNodeFactory();
1206+
popNodeFactoryCheckpoint(checkpoint);
12001207
if (optionalMangledTypeName.has_value()) {
12011208
auto mangledTypeName = optionalMangledTypeName.value();
12021209
if (forMangledTypeName.has_value()) {
@@ -2011,10 +2018,11 @@ class TypeRefBuilder {
20112018
std::unordered_map<std::string, std::string> typeNameToManglingMap;
20122019
for (const auto &section : ReflectionInfos) {
20132020
for (auto descriptor : section.Field) {
2021+
auto checkpoint = pushNodeFactoryCheckpoint();
20142022
auto TypeRef = readTypeRef(descriptor, descriptor->MangledTypeName);
20152023
auto OptionalMangledTypeName = normalizeReflectionName(TypeRef);
20162024
auto TypeName = nodeToString(demangleTypeRef(TypeRef));
2017-
clearNodeFactory();
2025+
popNodeFactoryCheckpoint(checkpoint);
20182026
if (OptionalMangledTypeName.has_value()) {
20192027
typeNameToManglingMap[TypeName] = OptionalMangledTypeName.value();
20202028
}

lib/Demangling/Demangler.cpp

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,13 +505,91 @@ void NodeFactory::clear() {
505505
// cycles.
506506
CurrentSlab->Previous = nullptr;
507507
CurPtr = (char *)(CurrentSlab + 1);
508-
assert(End == CurPtr + SlabSize);
509508
#ifdef NODE_FACTORY_DEBUGGING
510509
allocatedMemory = 0;
511510
#endif
512511
}
513512
}
514513

514+
NodeFactory::Checkpoint NodeFactory::pushCheckpoint() const {
515+
return {CurrentSlab, CurPtr, End};
516+
}
517+
518+
void NodeFactory::popCheckpoint(NodeFactory::Checkpoint checkpoint) {
519+
if (checkpoint.Slab == CurrentSlab) {
520+
if (checkpoint.CurPtr > CurPtr) {
521+
fatal(0,
522+
"Popping checkpoint {%p, %p, %p} that is after the current "
523+
"pointer.\n",
524+
checkpoint.Slab, checkpoint.CurPtr, checkpoint.End);
525+
}
526+
if (checkpoint.End != End) {
527+
fatal(0,
528+
"Popping checkpoint {%p, %p, %p} with End that does not match "
529+
"current End %p.\n",
530+
checkpoint.Slab, checkpoint.CurPtr, checkpoint.End, End);
531+
}
532+
#ifndef NDEBUG
533+
// Scribble the newly freed area.
534+
memset(checkpoint.CurPtr, 0xaa, CurPtr - checkpoint.CurPtr);
535+
#endif
536+
CurPtr = checkpoint.CurPtr;
537+
} else {
538+
// We may want to keep using the current slab rather than destroying
539+
// it, since over time this allows us to converge on a steady state
540+
// with no allocation activity (see the comment above in
541+
// NodeFactory::clear). Keep using the current slab if the free space in the
542+
// checkpoint's slab is less than 1/16th of the current slab's space. We
543+
// won't repeatedly allocate and deallocate the current slab. The size
544+
// doubles each time so we'll quickly pass the threshold.
545+
Slab *savedSlab = nullptr;
546+
if (CurrentSlab) {
547+
size_t checkpointSlabFreeSpace = checkpoint.End - checkpoint.CurPtr;
548+
size_t currentSlabSize = End - (char *)(CurrentSlab + 1);
549+
if (currentSlabSize / 16 > checkpointSlabFreeSpace) {
550+
savedSlab = CurrentSlab;
551+
CurrentSlab = CurrentSlab->Previous;
552+
// No need to save End, as it will still be in place later.
553+
}
554+
}
555+
556+
// Free all slabs (possibly except the one we saved) until we find the end
557+
// or we find the checkpoint.
558+
while (CurrentSlab && checkpoint.Slab != CurrentSlab) {
559+
auto Slab = CurrentSlab;
560+
CurrentSlab = CurrentSlab->Previous;
561+
free(Slab);
562+
}
563+
564+
// If we ran to the end and the checkpoint actually has a slab pointer, then
565+
// the checkpoint is invalid.
566+
if (!CurrentSlab && checkpoint.Slab) {
567+
fatal(0,
568+
"Popping checkpoint {%p, %p, %p} with slab that is not within "
569+
"the allocator's slab chain.\n",
570+
checkpoint.Slab, checkpoint.CurPtr, checkpoint.End);
571+
}
572+
573+
if (savedSlab) {
574+
// Reinstall the saved slab.
575+
savedSlab->Previous = CurrentSlab;
576+
CurrentSlab = savedSlab;
577+
CurPtr = (char *)(CurrentSlab + 1);
578+
// End is still valid from before.
579+
} else {
580+
// Set CurPtr and End to match the checkpoint's position.
581+
CurPtr = checkpoint.CurPtr;
582+
End = checkpoint.End;
583+
}
584+
585+
#ifndef NDEBUG
586+
// Scribble the now freed end of the slab.
587+
if (CurPtr)
588+
memset(CurPtr, 0xaa, End - CurPtr);
589+
#endif
590+
}
591+
}
592+
515593
NodePointer NodeFactory::createNode(Node::Kind K) {
516594
return new (Allocate<Node>()) Node(K);
517595
}

stdlib/public/RemoteInspection/TypeRefBuilder.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ RemoteRef<char> TypeRefBuilder::readTypeRef(uint64_t remoteAddr) {
9393
/// Load and normalize a mangled name so it can be matched with string equality.
9494
llvm::Optional<std::string>
9595
TypeRefBuilder::normalizeReflectionName(RemoteRef<char> reflectionName) {
96+
auto checkpoint = pushNodeFactoryCheckpoint();
9697
// Remangle the reflection name to resolve symbolic references.
9798
if (auto node = demangleTypeRef(reflectionName,
9899
/*useOpaqueTypeSymbolicReferences*/ false)) {
@@ -101,16 +102,18 @@ TypeRefBuilder::normalizeReflectionName(RemoteRef<char> reflectionName) {
101102
case Node::Kind::ProtocolSymbolicReference:
102103
case Node::Kind::OpaqueTypeDescriptorSymbolicReference:
103104
// Symbolic references cannot be mangled, return a failure.
105+
popNodeFactoryCheckpoint(checkpoint);
104106
return {};
105107
default:
106108
auto mangling = mangleNode(node);
107-
clearNodeFactory();
109+
popNodeFactoryCheckpoint(checkpoint);
108110
if (!mangling.isSuccess()) {
109111
return {};
110112
}
111113
return std::move(mangling.result());
112114
}
113115
}
116+
popNodeFactoryCheckpoint(checkpoint);
114117

115118
// Fall back to the raw string.
116119
return getTypeRefString(reflectionName).str();
@@ -159,11 +162,12 @@ lookupTypeWitness(const std::string &MangledTypeName,
159162
0)
160163
continue;
161164

165+
auto checkpoint = pushNodeFactoryCheckpoint();
162166
auto SubstitutedTypeName = readTypeRef(AssocTy,
163167
AssocTy->SubstitutedTypeName);
164168
auto Demangled = demangleTypeRef(SubstitutedTypeName);
165169
auto *TypeWitness = decodeMangledType(Demangled);
166-
clearNodeFactory();
170+
popNodeFactoryCheckpoint(checkpoint);
167171

168172
AssociatedTypeCache.insert(std::make_pair(key, TypeWitness));
169173
return TypeWitness;
@@ -181,9 +185,10 @@ const TypeRef *TypeRefBuilder::lookupSuperclass(const TypeRef *TR) {
181185
if (!FD->hasSuperclass())
182186
return nullptr;
183187

188+
auto checkpoint = pushNodeFactoryCheckpoint();
184189
auto Demangled = demangleTypeRef(readTypeRef(FD, FD->Superclass));
185190
auto Unsubstituted = decodeMangledType(Demangled);
186-
clearNodeFactory();
191+
popNodeFactoryCheckpoint(checkpoint);
187192
if (!Unsubstituted)
188193
return nullptr;
189194

@@ -367,9 +372,10 @@ bool TypeRefBuilder::getFieldTypeRefs(
367372
continue;
368373
}
369374

375+
auto checkpoint = pushNodeFactoryCheckpoint();
370376
auto Demangled = demangleTypeRef(readTypeRef(Field,Field->MangledTypeName));
371377
auto Unsubstituted = decodeMangledType(Demangled);
372-
clearNodeFactory();
378+
popNodeFactoryCheckpoint(checkpoint);
373379
if (!Unsubstituted)
374380
return false;
375381

@@ -486,10 +492,11 @@ TypeRefBuilder::getClosureContextInfo(RemoteRef<CaptureDescriptor> CD) {
486492
auto CR = CD.getField(*i);
487493

488494
if (CR->hasMangledTypeName()) {
495+
auto checkpoint = pushNodeFactoryCheckpoint();
489496
auto MangledName = readTypeRef(CR, CR->MangledTypeName);
490497
auto DemangleTree = demangleTypeRef(MangledName);
491498
TR = decodeMangledType(DemangleTree);
492-
clearNodeFactory();
499+
popNodeFactoryCheckpoint(checkpoint);
493500
}
494501
Info.CaptureTypes.push_back(TR);
495502
}
@@ -499,10 +506,11 @@ TypeRefBuilder::getClosureContextInfo(RemoteRef<CaptureDescriptor> CD) {
499506
auto MSR = CD.getField(*i);
500507

501508
if (MSR->hasMangledTypeName()) {
509+
auto checkpoint = pushNodeFactoryCheckpoint();
502510
auto MangledName = readTypeRef(MSR, MSR->MangledTypeName);
503511
auto DemangleTree = demangleTypeRef(MangledName);
504512
TR = decodeMangledType(DemangleTree);
505-
clearNodeFactory();
513+
popNodeFactoryCheckpoint(checkpoint);
506514
}
507515

508516
const MetadataSource *MS = nullptr;
@@ -526,11 +534,12 @@ TypeRefBuilder::getClosureContextInfo(RemoteRef<CaptureDescriptor> CD) {
526534

527535
void TypeRefBuilder::dumpTypeRef(RemoteRef<char> MangledName,
528536
std::ostream &stream, bool printTypeName) {
537+
auto checkpoint = pushNodeFactoryCheckpoint();
529538
auto DemangleTree = demangleTypeRef(MangledName);
530539
auto TypeName = nodeToString(DemangleTree);
531540
stream << TypeName << "\n";
532541
auto Result = swift::Demangle::decodeMangledType(*this, DemangleTree);
533-
clearNodeFactory();
542+
popNodeFactoryCheckpoint(checkpoint);
534543
if (Result.isError()) {
535544
auto *Error = Result.getError();
536545
char *ErrorStr = Error->copyErrorString();
@@ -549,10 +558,11 @@ FieldTypeCollectionResult TypeRefBuilder::collectFieldTypes(
549558
FieldTypeCollectionResult result;
550559
for (const auto &sections : ReflectionInfos) {
551560
for (auto descriptor : sections.Field) {
561+
auto checkpoint = pushNodeFactoryCheckpoint();
552562
auto typeRef = readTypeRef(descriptor, descriptor->MangledTypeName);
553563
auto typeName = nodeToString(demangleTypeRef(typeRef));
554564
auto optionalMangledTypeName = normalizeReflectionName(typeRef);
555-
clearNodeFactory();
565+
popNodeFactoryCheckpoint(checkpoint);
556566
if (optionalMangledTypeName.has_value()) {
557567
auto mangledTypeName =
558568
optionalMangledTypeName.value();
@@ -618,10 +628,11 @@ void TypeRefBuilder::dumpFieldSection(std::ostream &stream) {
618628
void TypeRefBuilder::dumpBuiltinTypeSection(std::ostream &stream) {
619629
for (const auto &sections : ReflectionInfos) {
620630
for (auto descriptor : sections.Builtin) {
631+
auto checkpoint = pushNodeFactoryCheckpoint();
621632
auto typeNode =
622633
demangleTypeRef(readTypeRef(descriptor, descriptor->TypeName));
623634
auto typeName = nodeToString(typeNode);
624-
clearNodeFactory();
635+
popNodeFactoryCheckpoint(checkpoint);
625636

626637
stream << "\n- " << typeName << ":\n";
627638
stream << "Size: " << descriptor->Size << "\n";
@@ -670,10 +681,11 @@ void TypeRefBuilder::dumpCaptureSection(std::ostream &stream) {
670681
void TypeRefBuilder::dumpMultiPayloadEnumSection(std::ostream &stream) {
671682
for (const auto &sections : ReflectionInfos) {
672683
for (const auto descriptor : sections.MultiPayloadEnum) {
684+
auto checkpoint = pushNodeFactoryCheckpoint();
673685
auto typeNode =
674686
demangleTypeRef(readTypeRef(descriptor, descriptor->TypeName));
675687
auto typeName = nodeToString(typeNode);
676-
clearNodeFactory();
688+
popNodeFactoryCheckpoint(checkpoint);
677689

678690
stream << "\n- " << typeName << ":\n";
679691
stream << " Descriptor Size: " << descriptor->getSizeInBytes() << "\n";

0 commit comments

Comments
 (0)