mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
When using VS Code, it semi-frequently happens that sourcekitd is reading the output file map and the memory buffer containing the memory buffer containing the output file map is not complete and terminates at distinct powers of 2. I managed to attach a debugger once and the memory buffer containing the output file had size 0x3000 = 12288 while the actual output file map is 20319 bytes large. One hypothesis for this is that the read of output file map is racing with a write from a SwiftPM build but I haven’t been able to confirm it. In either case, the result is that the output file map buffer ends with an unterminated string literal, which causes `getKey()` or `getValue()` in the JSON parser to return `nullptr` and then consequently hits an assertion failure in `dyn_cast`. Add a check for nullptr before invoking `dyn_cast` just like we do it in the outer `for` loop. rdar://122364031
250 lines
8.2 KiB
C++
250 lines
8.2 KiB
C++
//===--- OutputFileMap.cpp - Map of inputs to multiple outputs ------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/Basic/OutputFileMap.h"
|
|
#include "swift/Basic/FileTypes.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <system_error>
|
|
|
|
using namespace swift;
|
|
|
|
llvm::Expected<OutputFileMap>
|
|
OutputFileMap::loadFromPath(StringRef Path, StringRef workingDirectory) {
|
|
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileBufOrErr =
|
|
llvm::MemoryBuffer::getFile(Path);
|
|
if (!FileBufOrErr) {
|
|
return llvm::errorCodeToError(FileBufOrErr.getError());
|
|
}
|
|
return loadFromBuffer(std::move(FileBufOrErr.get()), workingDirectory);
|
|
}
|
|
|
|
llvm::Expected<OutputFileMap>
|
|
OutputFileMap::loadFromBuffer(StringRef Data, StringRef workingDirectory) {
|
|
std::unique_ptr<llvm::MemoryBuffer> Buffer{
|
|
llvm::MemoryBuffer::getMemBuffer(Data)};
|
|
return loadFromBuffer(std::move(Buffer), workingDirectory);
|
|
}
|
|
|
|
llvm::Expected<OutputFileMap>
|
|
OutputFileMap::loadFromBuffer(std::unique_ptr<llvm::MemoryBuffer> Buffer,
|
|
StringRef workingDirectory) {
|
|
return parse(std::move(Buffer), workingDirectory);
|
|
}
|
|
|
|
const TypeToPathMap *OutputFileMap::getOutputMapForInput(StringRef Input) const{
|
|
auto iter = InputToOutputsMap.find(Input);
|
|
if (iter == InputToOutputsMap.end())
|
|
return nullptr;
|
|
else
|
|
return &iter->second;
|
|
}
|
|
|
|
TypeToPathMap &
|
|
OutputFileMap::getOrCreateOutputMapForInput(StringRef Input) {
|
|
return InputToOutputsMap[Input];
|
|
}
|
|
|
|
const TypeToPathMap *OutputFileMap::getOutputMapForSingleOutput() const {
|
|
return getOutputMapForInput(StringRef());
|
|
}
|
|
|
|
TypeToPathMap &
|
|
OutputFileMap::getOrCreateOutputMapForSingleOutput() {
|
|
return InputToOutputsMap[StringRef()];
|
|
}
|
|
|
|
void OutputFileMap::dump(llvm::raw_ostream &os, bool Sort) const {
|
|
using TypePathPair = std::pair<file_types::ID, std::string>;
|
|
|
|
auto printOutputPair = [&os](StringRef InputPath,
|
|
const TypePathPair &OutputPair) -> void {
|
|
os << InputPath << " -> " << file_types::getTypeName(OutputPair.first)
|
|
<< ": \"" << OutputPair.second << "\"\n";
|
|
};
|
|
|
|
if (Sort) {
|
|
using PathMapPair = std::pair<StringRef, TypeToPathMap>;
|
|
std::vector<PathMapPair> Maps;
|
|
for (auto &InputPair : InputToOutputsMap) {
|
|
Maps.emplace_back(InputPair.first(), InputPair.second);
|
|
}
|
|
std::sort(Maps.begin(), Maps.end(), [] (const PathMapPair &LHS,
|
|
const PathMapPair &RHS) -> bool {
|
|
return LHS.first < RHS.first;
|
|
});
|
|
for (auto &InputPair : Maps) {
|
|
const TypeToPathMap &Map = InputPair.second;
|
|
std::vector<TypePathPair> Pairs;
|
|
Pairs.insert(Pairs.end(), Map.begin(), Map.end());
|
|
std::sort(Pairs.begin(), Pairs.end(), [](const TypePathPair &LHS,
|
|
const TypePathPair &RHS) -> bool {
|
|
return LHS < RHS;
|
|
});
|
|
for (auto &OutputPair : Pairs) {
|
|
printOutputPair(InputPair.first, OutputPair);
|
|
}
|
|
}
|
|
} else {
|
|
for (auto &InputPair : InputToOutputsMap) {
|
|
const TypeToPathMap &Map = InputPair.second;
|
|
for (const TypePathPair &OutputPair : Map) {
|
|
printOutputPair(InputPair.first(), OutputPair);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void writeQuotedEscaped(llvm::raw_ostream &os,
|
|
const StringRef fileName) {
|
|
os << "\"" << llvm::yaml::escape(fileName) << "\"";
|
|
}
|
|
|
|
void OutputFileMap::write(llvm::raw_ostream &os,
|
|
ArrayRef<StringRef> inputs) const {
|
|
for (const auto &input : inputs) {
|
|
writeQuotedEscaped(os, input);
|
|
os << ":";
|
|
|
|
const TypeToPathMap *outputMap = getOutputMapForInput(input);
|
|
if (!outputMap) {
|
|
// The map doesn't have an entry for this input. (Perhaps there were no
|
|
// outputs and thus the entry was never created.) Put an empty sub-map
|
|
// into the output and move on.
|
|
os << " {}\n";
|
|
continue;
|
|
}
|
|
|
|
os << "\n";
|
|
// DenseMap is unordered. If you write a test, please sort the output.
|
|
for (auto &typeAndOutputPath : *outputMap) {
|
|
file_types::ID type = typeAndOutputPath.getFirst();
|
|
StringRef output = typeAndOutputPath.getSecond();
|
|
os << " " << file_types::getTypeName(type) << ": ";
|
|
writeQuotedEscaped(os, output);
|
|
os << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
llvm::Expected<OutputFileMap>
|
|
OutputFileMap::parse(std::unique_ptr<llvm::MemoryBuffer> Buffer,
|
|
StringRef workingDirectory) {
|
|
auto constructError =
|
|
[](const char *errorString) -> llvm::Expected<OutputFileMap> {
|
|
return llvm::make_error<llvm::StringError>(errorString,
|
|
llvm::inconvertibleErrorCode());
|
|
};
|
|
/// FIXME: Make the returned error strings more specific by including some of
|
|
/// the source.
|
|
llvm::SourceMgr SM;
|
|
llvm::yaml::Stream YAMLStream(Buffer->getMemBufferRef(), SM);
|
|
auto I = YAMLStream.begin();
|
|
if (I == YAMLStream.end())
|
|
return constructError("empty YAML stream");
|
|
|
|
auto Root = I->getRoot();
|
|
if (!Root)
|
|
return constructError("no root");
|
|
|
|
OutputFileMap OFM;
|
|
|
|
auto *Map = dyn_cast<llvm::yaml::MappingNode>(Root);
|
|
if (!Map)
|
|
return constructError("root was not a MappingNode");
|
|
|
|
auto resolvePath =
|
|
[workingDirectory](
|
|
llvm::yaml::ScalarNode *Path,
|
|
llvm::SmallVectorImpl<char> &PathStorage) -> StringRef {
|
|
StringRef PathStr = Path->getValue(PathStorage);
|
|
if (workingDirectory.empty() || PathStr.empty() || PathStr == "-" ||
|
|
llvm::sys::path::is_absolute(PathStr)) {
|
|
return PathStr;
|
|
}
|
|
// Copy the path to avoid making assumptions about how getValue deals with
|
|
// Storage.
|
|
SmallString<128> PathStrCopy(PathStr);
|
|
PathStorage.clear();
|
|
PathStorage.reserve(PathStrCopy.size() + workingDirectory.size() + 1);
|
|
PathStorage.insert(PathStorage.begin(), workingDirectory.begin(),
|
|
workingDirectory.end());
|
|
llvm::sys::path::append(PathStorage, PathStrCopy);
|
|
return StringRef(PathStorage.data(), PathStorage.size());
|
|
};
|
|
|
|
for (auto &Pair : *Map) {
|
|
llvm::yaml::Node *Key = Pair.getKey();
|
|
llvm::yaml::Node *Value = Pair.getValue();
|
|
|
|
if (!Key)
|
|
return constructError("bad key");
|
|
|
|
if (!Value)
|
|
return constructError("bad value");
|
|
|
|
auto *InputPath = dyn_cast<llvm::yaml::ScalarNode>(Key);
|
|
if (!InputPath)
|
|
return constructError("input path not a scalar node");
|
|
|
|
llvm::yaml::MappingNode *OutputMapNode =
|
|
dyn_cast<llvm::yaml::MappingNode>(Value);
|
|
if (!OutputMapNode)
|
|
return constructError("output map not a MappingNode");
|
|
|
|
TypeToPathMap OutputMap;
|
|
|
|
for (auto &OutputPair : *OutputMapNode) {
|
|
llvm::yaml::Node *Key = OutputPair.getKey();
|
|
llvm::yaml::Node *Value = OutputPair.getValue();
|
|
|
|
if (!Key)
|
|
return constructError("bad kind");
|
|
|
|
if (!Value)
|
|
return constructError("bad path");
|
|
|
|
auto *KindNode = dyn_cast<llvm::yaml::ScalarNode>(Key);
|
|
if (!KindNode)
|
|
return constructError("kind not a ScalarNode");
|
|
|
|
auto *Path = dyn_cast<llvm::yaml::ScalarNode>(Value);
|
|
if (!Path)
|
|
return constructError("path not a scalar node");
|
|
|
|
llvm::SmallString<16> KindStorage;
|
|
file_types::ID Kind =
|
|
file_types::lookupTypeForName(KindNode->getValue(KindStorage));
|
|
|
|
// Ignore unknown types, so that an older swiftc can be used with a newer
|
|
// build system.
|
|
if (Kind == file_types::TY_INVALID)
|
|
continue;
|
|
|
|
llvm::SmallString<128> PathStorage;
|
|
OutputMap.insert(std::pair<file_types::ID, std::string>(
|
|
Kind, resolvePath(Path, PathStorage).str()));
|
|
}
|
|
|
|
llvm::SmallString<128> InputStorage;
|
|
OFM.InputToOutputsMap[resolvePath(InputPath, InputStorage)] =
|
|
std::move(OutputMap);
|
|
}
|
|
|
|
if (YAMLStream.failed())
|
|
return constructError("Output file map parse failed");
|
|
|
|
return OFM;
|
|
}
|