Files
clang-p2996/libc/utils/HdrGen/Generator.cpp
Stephen Neuendorffer 6d0174e706 [libc] allow libc-hdrgen to work on windows files (#87292)
The code does some (overly simple?) checks on file syntax. These checks
assume unix line endings and fail on windows. This commit updates the
code to strip extra whitespace, making the checks more robust,
particularly in the presence of windows line endings.

Fixes #86023
2024-04-01 17:04:29 -07:00

203 lines
7.0 KiB
C++

//===-- Implementation of the main header generation class ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Generator.h"
#include "IncludeFileCommand.h"
#include "PublicAPICommand.h"
#include "utils/LibcTableGenUtil/APIIndexer.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include <cstdlib>
#include <memory>
static const char CommandPrefix[] = "%%";
static const size_t CommandPrefixSize = llvm::StringRef(CommandPrefix).size();
static const char CommentPrefix[] = "<!>";
static const char ParamNamePrefix[] = "${";
static const size_t ParamNamePrefixSize =
llvm::StringRef(ParamNamePrefix).size();
static const char ParamNameSuffix[] = "}";
static const size_t ParamNameSuffixSize =
llvm::StringRef(ParamNameSuffix).size();
namespace llvm_libc {
Command *Generator::getCommandHandler(llvm::StringRef CommandName) {
if (CommandName == IncludeFileCommand::Name) {
if (!IncludeFileCmd)
IncludeFileCmd = std::make_unique<IncludeFileCommand>();
return IncludeFileCmd.get();
} else if (CommandName == PublicAPICommand::Name) {
if (!PublicAPICmd)
PublicAPICmd = std::make_unique<PublicAPICommand>(EntrypointNameList);
return PublicAPICmd.get();
} else {
return nullptr;
}
}
void Generator::parseCommandArgs(llvm::StringRef ArgStr, ArgVector &Args) {
if (!ArgStr.contains(',') && ArgStr.trim(' ').trim('\t').size() == 0) {
// If it is just space between the parenthesis
return;
}
ArgStr.split(Args, ",");
for (llvm::StringRef &A : Args) {
A = A.trim(' ');
if (A.starts_with(ParamNamePrefix) && A.ends_with(ParamNameSuffix)) {
A = A.drop_front(ParamNamePrefixSize).drop_back(ParamNameSuffixSize);
A = ArgMap[std::string(A)];
}
}
}
void Generator::generate(llvm::raw_ostream &OS, llvm::RecordKeeper &Records) {
auto DefFileBuffer = llvm::MemoryBuffer::getFile(HeaderDefFile);
if (!DefFileBuffer) {
llvm::errs() << "Unable to open " << HeaderDefFile << ".\n";
std::exit(1);
}
llvm::SourceMgr SrcMgr;
unsigned DefFileID = SrcMgr.AddNewSourceBuffer(
std::move(DefFileBuffer.get()), llvm::SMLoc::getFromPointer(nullptr));
llvm::StringRef Content = SrcMgr.getMemoryBuffer(DefFileID)->getBuffer();
while (true) {
std::pair<llvm::StringRef, llvm::StringRef> P = Content.split('\n');
Content = P.second;
llvm::StringRef Line = P.first.trim(' ');
if (Line.starts_with(CommandPrefix)) {
Line = Line.drop_front(CommandPrefixSize);
P = Line.split("(");
// It's possible that we have windows line endings, so strip off the extra
// CR.
P.second = P.second.trim();
if (P.second.empty() || P.second[P.second.size() - 1] != ')') {
SrcMgr.PrintMessage(llvm::SMLoc::getFromPointer(P.second.data()),
llvm::SourceMgr::DK_Error,
"Command argument list should begin with '(' "
"and end with ')'.");
SrcMgr.PrintMessage(llvm::SMLoc::getFromPointer(P.second.data()),
llvm::SourceMgr::DK_Error, P.second.data());
SrcMgr.PrintMessage(llvm::SMLoc::getFromPointer(P.second.data()),
llvm::SourceMgr::DK_Error,
std::to_string(P.second.size()));
std::exit(1);
}
llvm::StringRef CommandName = P.first;
Command *Cmd = getCommandHandler(CommandName);
if (Cmd == nullptr) {
SrcMgr.PrintMessage(llvm::SMLoc::getFromPointer(CommandName.data()),
llvm::SourceMgr::DK_Error,
"Unknown command '%%" + CommandName + "'.");
std::exit(1);
}
llvm::StringRef ArgStr = P.second.drop_back(1);
ArgVector Args;
parseCommandArgs(ArgStr, Args);
Command::ErrorReporter Reporter(
llvm::SMLoc::getFromPointer(CommandName.data()), SrcMgr);
Cmd->run(OS, Args, StdHeader, Records, Reporter);
} else if (!Line.starts_with(CommentPrefix)) {
// There is no comment or command on this line so we just write it as is.
OS << P.first << "\n";
}
if (P.second.empty())
break;
}
}
void Generator::generateDecls(llvm::raw_ostream &OS,
llvm::RecordKeeper &Records) {
OS << "//===-- C standard declarations for " << StdHeader << " "
<< std::string(80 - (42 + StdHeader.size()), '-') << "===//\n"
<< "//\n"
<< "// Part of the LLVM Project, under the Apache License v2.0 with LLVM "
"Exceptions.\n"
<< "// See https://llvm.org/LICENSE.txt for license information.\n"
<< "// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception\n"
<< "//\n"
<< "//"
"===-------------------------------------------------------------------"
"---===//\n\n";
std::string HeaderGuard(StdHeader.size(), '\0');
llvm::transform(StdHeader, HeaderGuard.begin(), [](const char C) -> char {
return !isalnum(C) ? '_' : llvm::toUpper(C);
});
OS << "#ifndef __LLVM_LIBC_DECLARATIONS_" << HeaderGuard << "\n"
<< "#define __LLVM_LIBC_DECLARATIONS_" << HeaderGuard << "\n\n";
OS << "#ifndef __LIBC_ATTRS\n"
<< "#define __LIBC_ATTRS\n"
<< "#endif\n\n";
OS << "#ifdef __cplusplus\n"
<< "extern \"C\" {\n"
<< "#endif\n\n";
APIIndexer G(StdHeader, Records);
for (auto &Name : EntrypointNameList) {
// Filter out functions not exported by this header.
if (G.FunctionSpecMap.find(Name) == G.FunctionSpecMap.end())
continue;
llvm::Record *FunctionSpec = G.FunctionSpecMap[Name];
llvm::Record *RetValSpec = FunctionSpec->getValueAsDef("Return");
llvm::Record *ReturnType = RetValSpec->getValueAsDef("ReturnType");
OS << G.getTypeAsString(ReturnType) << " " << Name << "(";
auto ArgsList = FunctionSpec->getValueAsListOfDefs("Args");
for (size_t i = 0; i < ArgsList.size(); ++i) {
llvm::Record *ArgType = ArgsList[i]->getValueAsDef("ArgType");
OS << G.getTypeAsString(ArgType);
if (i < ArgsList.size() - 1)
OS << ", ";
}
OS << ") __LIBC_ATTRS;\n\n";
}
// Make another pass over entrypoints to emit object declarations.
for (const auto &Name : EntrypointNameList) {
if (G.ObjectSpecMap.find(Name) == G.ObjectSpecMap.end())
continue;
llvm::Record *ObjectSpec = G.ObjectSpecMap[Name];
auto Type = ObjectSpec->getValueAsString("Type");
OS << "extern " << Type << " " << Name << " __LIBC_ATTRS;\n";
}
// Emit a final newline if we emitted any object declarations.
if (llvm::any_of(EntrypointNameList, [&](const std::string &Name) {
return G.ObjectSpecMap.find(Name) != G.ObjectSpecMap.end();
}))
OS << "\n";
OS << "#ifdef __cplusplus\n"
<< "}\n"
<< "#endif\n\n";
OS << "#endif\n";
}
} // namespace llvm_libc