Skip to content

[stable/20220421] SymbolGraph ExtractAPI support for C and Objective-C in clang #4453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion clang/include/clang/ExtractAPI/FrontendActions.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ class ExtractAPIAction : public ASTFrontendAction {
std::unique_ptr<llvm::MemoryBuffer> Buffer;

/// The input file originally provided on the command line.
std::vector<std::string> KnownInputFiles;
///
/// This captures the spelling used to include the file and whether the
/// include is quoted or not.
SmallVector<std::pair<SmallString<32>, bool>> KnownInputFiles;

/// Prepare to execute the action on the given CompilerInstance.
///
Expand Down
191 changes: 179 additions & 12 deletions clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/raw_ostream.h"
#include <memory>
#include <utility>
Expand All @@ -55,10 +58,125 @@ StringRef getTypedefName(const TagDecl *Decl) {
return {};
}

Optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
StringRef File,
bool *IsQuoted = nullptr) {
assert(CI.hasFileManager() &&
"CompilerInstance does not have a FileNamager!");

using namespace llvm::sys;
// Matches framework include patterns
const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)");

const auto &FS = CI.getVirtualFileSystem();

SmallString<128> FilePath(File.begin(), File.end());
FS.makeAbsolute(FilePath);
path::remove_dots(FilePath, true);
FilePath = path::convert_to_slash(FilePath);
File = FilePath;

// Checks whether `Dir` is a strict path prefix of `File`. If so returns
// the prefix length. Otherwise return 0.
auto CheckDir = [&](llvm::StringRef Dir) -> unsigned {
llvm::SmallString<32> DirPath(Dir.begin(), Dir.end());
FS.makeAbsolute(DirPath);
path::remove_dots(DirPath, true);
Dir = DirPath;
for (auto NI = path::begin(File), NE = path::end(File),
DI = path::begin(Dir), DE = path::end(Dir);
/*termination condition in loop*/; ++NI, ++DI) {
// '.' components in File are ignored.
while (NI != NE && *NI == ".")
++NI;
if (NI == NE)
break;

// '.' components in Dir are ignored.
while (DI != DE && *DI == ".")
++DI;

// Dir is a prefix of File, up to '.' components and choice of path
// separators.
if (DI == DE)
return NI - path::begin(File);

// Consider all path separators equal.
if (NI->size() == 1 && DI->size() == 1 &&
path::is_separator(NI->front()) && path::is_separator(DI->front()))
continue;

// Special case Apple .sdk folders since the search path is typically a
// symlink like `iPhoneSimulator14.5.sdk` while the file is instead
// located in `iPhoneSimulator.sdk` (the real folder).
if (NI->endswith(".sdk") && DI->endswith(".sdk")) {
StringRef NBasename = path::stem(*NI);
StringRef DBasename = path::stem(*DI);
if (DBasename.startswith(NBasename))
continue;
}

if (*NI != *DI)
break;
}
return 0;
};

unsigned PrefixLength = 0;

// Go through the search paths and find the first one that is a prefix of
// the header.
for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries) {
// Note whether the match is found in a quoted entry.
if (IsQuoted)
*IsQuoted = Entry.Group == frontend::Quoted;

if (auto EntryFile = CI.getFileManager().getOptionalFileRef(Entry.Path)) {
if (auto HMap = HeaderMap::Create(*EntryFile, CI.getFileManager())) {
// If this is a headermap entry, try to reverse lookup the full path
// for a spelled name before mapping.
StringRef SpelledFilename = HMap->reverseLookupFilename(File);
if (!SpelledFilename.empty())
return SpelledFilename.str();

// No matching mapping in this headermap, try next search entry.
continue;
}
}

// Entry is a directory search entry, try to check if it's a prefix of File.
PrefixLength = CheckDir(Entry.Path);
if (PrefixLength > 0) {
// The header is found in a framework path, construct the framework-style
// include name `<Framework/Header.h>`
if (Entry.IsFramework) {
SmallVector<StringRef, 4> Matches;
Rule.match(File, &Matches);
// Returned matches are always in stable order.
if (Matches.size() != 4)
return None;

return path::convert_to_slash(
(Matches[1].drop_front(Matches[1].rfind('/') + 1) + "/" +
Matches[3])
.str());
}

// The header is found in a normal search path, strip the search path
// prefix to get an include name.
return path::convert_to_slash(File.drop_front(PrefixLength));
}
}

// Couldn't determine a include name, use full path instead.
return None;
}

struct LocationFileChecker {
bool isLocationInKnownFile(SourceLocation Loc) {
// If the loc refers to a macro expansion we need to first get the file
// location of the expansion.
auto &SM = CI.getSourceManager();
auto FileLoc = SM.getFileLoc(Loc);
FileID FID = SM.getFileID(FileLoc);
if (FID.isInvalid())
Expand All @@ -71,20 +189,44 @@ struct LocationFileChecker {
if (KnownFileEntries.count(File))
return true;

if (ExternalFileEntries.count(File))
return false;

StringRef FileName = File->tryGetRealPathName().empty()
? File->getName()
: File->tryGetRealPathName();

// Try to reduce the include name the same way we tried to include it.
bool IsQuoted = false;
if (auto IncludeName = getRelativeIncludeName(CI, FileName, &IsQuoted))
if (llvm::find_if(KnownFiles,
[&IsQuoted, &IncludeName](const auto &KnownFile) {
return KnownFile.first.equals(*IncludeName) &&
KnownFile.second == IsQuoted;
}) != KnownFiles.end()) {
KnownFileEntries.insert(File);
return true;
}

// Record that the file was not found to avoid future reverse lookup for
// the same file.
ExternalFileEntries.insert(File);
return false;
}

LocationFileChecker(const SourceManager &SM,
const std::vector<std::string> &KnownFiles)
: SM(SM) {
for (const auto &KnownFilePath : KnownFiles)
if (auto FileEntry = SM.getFileManager().getFile(KnownFilePath))
LocationFileChecker(const CompilerInstance &CI,
SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles)
: CI(CI), KnownFiles(KnownFiles), ExternalFileEntries() {
for (const auto &KnownFile : KnownFiles)
if (auto FileEntry = CI.getFileManager().getFile(KnownFile.first))
KnownFileEntries.insert(*FileEntry);
}

private:
const SourceManager &SM;
const CompilerInstance &CI;
SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles;
llvm::DenseSet<const FileEntry *> KnownFileEntries;
llvm::DenseSet<const FileEntry *> ExternalFileEntries;
};

/// The RecursiveASTVisitor to traverse symbol declarations and collect API
Expand Down Expand Up @@ -743,8 +885,7 @@ ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
CI.getTarget().getTriple(),
CI.getFrontendOpts().Inputs.back().getKind().getLanguage());

auto LCF = std::make_unique<LocationFileChecker>(CI.getSourceManager(),
KnownInputFiles);
auto LCF = std::make_unique<LocationFileChecker>(CI, KnownInputFiles);

CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(
CI.getSourceManager(), *LCF, *API, CI.getPreprocessor()));
Expand All @@ -758,22 +899,48 @@ bool ExtractAPIAction::PrepareToExecuteAction(CompilerInstance &CI) {
if (Inputs.empty())
return true;

if (!CI.hasFileManager())
if (!CI.createFileManager())
return false;

auto Kind = Inputs[0].getKind();

// Convert the header file inputs into a single input buffer.
SmallString<256> HeaderContents;
bool IsQuoted = false;
for (const FrontendInputFile &FIF : Inputs) {
if (Kind.isObjectiveC())
HeaderContents += "#import";
else
HeaderContents += "#include";
HeaderContents += " \"";
HeaderContents += FIF.getFile();
HeaderContents += "\"\n";

KnownInputFiles.emplace_back(FIF.getFile());
StringRef FilePath = FIF.getFile();
if (auto RelativeName = getRelativeIncludeName(CI, FilePath, &IsQuoted)) {
if (IsQuoted)
HeaderContents += " \"";
else
HeaderContents += " <";

HeaderContents += *RelativeName;

if (IsQuoted)
HeaderContents += "\"\n";
else
HeaderContents += ">\n";
KnownInputFiles.emplace_back(static_cast<SmallString<32>>(*RelativeName),
IsQuoted);
} else {
HeaderContents += " \"";
HeaderContents += FilePath;
HeaderContents += "\"\n";
KnownInputFiles.emplace_back(FilePath, true);
}
}

if (CI.getHeaderSearchOpts().Verbose)
CI.getVerboseOutputStream() << getInputBufferName() << ":\n"
<< HeaderContents << "\n";

Buffer = llvm::MemoryBuffer::getMemBufferCopy(HeaderContents,
getInputBufferName());

Expand Down
8 changes: 3 additions & 5 deletions clang/test/ExtractAPI/enum.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
// RUN: split-file %s %t
// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}@g" \
// RUN: %t/reference.output.json.in >> %t/reference.output.json
// RUN: %clang -extract-api -target arm64-apple-macosx \
// RUN: %t/input.h -o %t/output.json | FileCheck -allow-empty %s
// RUN: %clang_cc1 -extract-api -triple arm64-apple-macosx \
// RUN: -x c-header %t/input.h -o %t/output.json -verify

// Generator version is not consistent across test runs, normalize it.
// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \
// RUN: %t/output.json >> %t/output-normalized.json
// RUN: diff %t/reference.output.json %t/output-normalized.json

// CHECK-NOT: error:
// CHECK-NOT: warning:

//--- input.h
/// Kinds of vehicles
enum Vehicle {
Expand All @@ -37,6 +34,7 @@ enum {
enum {
OtherConstant = 2
};
// expected-no-diagnostics

//--- reference.output.json.in
{
Expand Down
Loading