Skip to content

Commit fbc3f69

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 fbc3f69

File tree

4 files changed

+152
-24
lines changed

4 files changed

+152
-24
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: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,26 @@ 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+
}
466+
467+
class ScopedNodeFactoryCheckpoint {
468+
TypeRefBuilder *Builder;
469+
NodeFactory::Checkpoint Checkpoint;
470+
471+
public:
472+
ScopedNodeFactoryCheckpoint(TypeRefBuilder *Builder)
473+
: Builder(Builder), Checkpoint(Builder->pushNodeFactoryCheckpoint()) {}
474+
475+
~ScopedNodeFactoryCheckpoint() {
476+
Builder->popNodeFactoryCheckpoint(Checkpoint);
477+
}
478+
};
460479

461480
BuiltType decodeMangledType(Node *node, bool forRequirement = true);
462481

@@ -1190,13 +1209,19 @@ class TypeRefBuilder {
11901209
for (auto descriptor : sections.AssociatedType) {
11911210
// Read out the relevant info from the associated type descriptor:
11921211
// The type's name and which protocol conformance it corresponds to
1193-
auto typeRef = readTypeRef(descriptor, descriptor->ConformingTypeName);
1194-
auto typeName = nodeToString(demangleTypeRef(typeRef));
1195-
auto optionalMangledTypeName = normalizeReflectionName(typeRef);
1196-
auto protocolNode = demangleTypeRef(
1197-
readTypeRef(descriptor, descriptor->ProtocolTypeName));
1198-
auto protocolName = nodeToString(protocolNode);
1199-
clearNodeFactory();
1212+
llvm::Optional<std::string> optionalMangledTypeName;
1213+
std::string typeName;
1214+
std::string protocolName;
1215+
{
1216+
ScopedNodeFactoryCheckpoint checkpoint(this);
1217+
auto typeRef =
1218+
readTypeRef(descriptor, descriptor->ConformingTypeName);
1219+
typeName = nodeToString(demangleTypeRef(typeRef));
1220+
optionalMangledTypeName = normalizeReflectionName(typeRef);
1221+
auto protocolNode = demangleTypeRef(
1222+
readTypeRef(descriptor, descriptor->ProtocolTypeName));
1223+
protocolName = nodeToString(protocolNode);
1224+
}
12001225
if (optionalMangledTypeName.has_value()) {
12011226
auto mangledTypeName = optionalMangledTypeName.value();
12021227
if (forMangledTypeName.has_value()) {
@@ -2011,10 +2036,10 @@ class TypeRefBuilder {
20112036
std::unordered_map<std::string, std::string> typeNameToManglingMap;
20122037
for (const auto &section : ReflectionInfos) {
20132038
for (auto descriptor : section.Field) {
2039+
ScopedNodeFactoryCheckpoint checkpoint(this);
20142040
auto TypeRef = readTypeRef(descriptor, descriptor->MangledTypeName);
20152041
auto OptionalMangledTypeName = normalizeReflectionName(TypeRef);
20162042
auto TypeName = nodeToString(demangleTypeRef(TypeRef));
2017-
clearNodeFactory();
20182043
if (OptionalMangledTypeName.has_value()) {
20192044
typeNameToManglingMap[TypeName] = OptionalMangledTypeName.value();
20202045
}

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: 17 additions & 13 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+
ScopedNodeFactoryCheckpoint checkpoint(this);
9697
// Remangle the reflection name to resolve symbolic references.
9798
if (auto node = demangleTypeRef(reflectionName,
9899
/*useOpaqueTypeSymbolicReferences*/ false)) {
@@ -104,7 +105,6 @@ TypeRefBuilder::normalizeReflectionName(RemoteRef<char> reflectionName) {
104105
return {};
105106
default:
106107
auto mangling = mangleNode(node);
107-
clearNodeFactory();
108108
if (!mangling.isSuccess()) {
109109
return {};
110110
}
@@ -159,11 +159,11 @@ lookupTypeWitness(const std::string &MangledTypeName,
159159
0)
160160
continue;
161161

162+
ScopedNodeFactoryCheckpoint checkpoint(this);
162163
auto SubstitutedTypeName = readTypeRef(AssocTy,
163164
AssocTy->SubstitutedTypeName);
164165
auto Demangled = demangleTypeRef(SubstitutedTypeName);
165166
auto *TypeWitness = decodeMangledType(Demangled);
166-
clearNodeFactory();
167167

168168
AssociatedTypeCache.insert(std::make_pair(key, TypeWitness));
169169
return TypeWitness;
@@ -181,9 +181,9 @@ const TypeRef *TypeRefBuilder::lookupSuperclass(const TypeRef *TR) {
181181
if (!FD->hasSuperclass())
182182
return nullptr;
183183

184+
ScopedNodeFactoryCheckpoint checkpoint(this);
184185
auto Demangled = demangleTypeRef(readTypeRef(FD, FD->Superclass));
185186
auto Unsubstituted = decodeMangledType(Demangled);
186-
clearNodeFactory();
187187
if (!Unsubstituted)
188188
return nullptr;
189189

@@ -367,9 +367,9 @@ bool TypeRefBuilder::getFieldTypeRefs(
367367
continue;
368368
}
369369

370+
ScopedNodeFactoryCheckpoint checkpoint(this);
370371
auto Demangled = demangleTypeRef(readTypeRef(Field,Field->MangledTypeName));
371372
auto Unsubstituted = decodeMangledType(Demangled);
372-
clearNodeFactory();
373373
if (!Unsubstituted)
374374
return false;
375375

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

488488
if (CR->hasMangledTypeName()) {
489+
ScopedNodeFactoryCheckpoint checkpoint(this);
489490
auto MangledName = readTypeRef(CR, CR->MangledTypeName);
490491
auto DemangleTree = demangleTypeRef(MangledName);
491492
TR = decodeMangledType(DemangleTree);
492-
clearNodeFactory();
493493
}
494494
Info.CaptureTypes.push_back(TR);
495495
}
@@ -499,10 +499,10 @@ TypeRefBuilder::getClosureContextInfo(RemoteRef<CaptureDescriptor> CD) {
499499
auto MSR = CD.getField(*i);
500500

501501
if (MSR->hasMangledTypeName()) {
502+
ScopedNodeFactoryCheckpoint checkpoint(this);
502503
auto MangledName = readTypeRef(MSR, MSR->MangledTypeName);
503504
auto DemangleTree = demangleTypeRef(MangledName);
504505
TR = decodeMangledType(DemangleTree);
505-
clearNodeFactory();
506506
}
507507

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

527527
void TypeRefBuilder::dumpTypeRef(RemoteRef<char> MangledName,
528528
std::ostream &stream, bool printTypeName) {
529+
ScopedNodeFactoryCheckpoint checkpoint(this);
529530
auto DemangleTree = demangleTypeRef(MangledName);
530531
auto TypeName = nodeToString(DemangleTree);
531532
stream << TypeName << "\n";
532533
auto Result = swift::Demangle::decodeMangledType(*this, DemangleTree);
533-
clearNodeFactory();
534534
if (Result.isError()) {
535535
auto *Error = Result.getError();
536536
char *ErrorStr = Error->copyErrorString();
@@ -549,10 +549,14 @@ FieldTypeCollectionResult TypeRefBuilder::collectFieldTypes(
549549
FieldTypeCollectionResult result;
550550
for (const auto &sections : ReflectionInfos) {
551551
for (auto descriptor : sections.Field) {
552-
auto typeRef = readTypeRef(descriptor, descriptor->MangledTypeName);
553-
auto typeName = nodeToString(demangleTypeRef(typeRef));
554-
auto optionalMangledTypeName = normalizeReflectionName(typeRef);
555-
clearNodeFactory();
552+
llvm::Optional<std::string> optionalMangledTypeName;
553+
std::string typeName;
554+
{
555+
ScopedNodeFactoryCheckpoint checkpoint(this);
556+
auto typeRef = readTypeRef(descriptor, descriptor->MangledTypeName);
557+
typeName = nodeToString(demangleTypeRef(typeRef));
558+
optionalMangledTypeName = normalizeReflectionName(typeRef);
559+
}
556560
if (optionalMangledTypeName.has_value()) {
557561
auto mangledTypeName =
558562
optionalMangledTypeName.value();
@@ -618,10 +622,10 @@ void TypeRefBuilder::dumpFieldSection(std::ostream &stream) {
618622
void TypeRefBuilder::dumpBuiltinTypeSection(std::ostream &stream) {
619623
for (const auto &sections : ReflectionInfos) {
620624
for (auto descriptor : sections.Builtin) {
625+
ScopedNodeFactoryCheckpoint checkpoint(this);
621626
auto typeNode =
622627
demangleTypeRef(readTypeRef(descriptor, descriptor->TypeName));
623628
auto typeName = nodeToString(typeNode);
624-
clearNodeFactory();
625629

626630
stream << "\n- " << typeName << ":\n";
627631
stream << "Size: " << descriptor->Size << "\n";
@@ -670,10 +674,10 @@ void TypeRefBuilder::dumpCaptureSection(std::ostream &stream) {
670674
void TypeRefBuilder::dumpMultiPayloadEnumSection(std::ostream &stream) {
671675
for (const auto &sections : ReflectionInfos) {
672676
for (const auto descriptor : sections.MultiPayloadEnum) {
677+
ScopedNodeFactoryCheckpoint checkpoint(this);
673678
auto typeNode =
674679
demangleTypeRef(readTypeRef(descriptor, descriptor->TypeName));
675680
auto typeName = nodeToString(typeNode);
676-
clearNodeFactory();
677681

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

0 commit comments

Comments
 (0)