sandboxed-api/sandboxed_api/tools/clang_generator/emitter.cc
Christian Blichmann 6d3adc45f7 clang_generator: Improve formatting of function prototype comments
This uses the Google formatting style to format the prototype comments, with an
internal line length of 75, which accomodates the indentation in the generated
API class.

PiperOrigin-RevId: 435303665
Change-Id: I4dcdf0ed773a79ebc55ead3843f07ca8556fd985
2022-03-17 03:53:13 -07:00

465 lines
15 KiB
C++

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sandboxed_api/tools/clang_generator/emitter.h"
#include "absl/random/random.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Type.h"
#include "clang/Format/Format.h"
#include "sandboxed_api/tools/clang_generator/diagnostics.h"
#include "sandboxed_api/tools/clang_generator/generator.h"
#include "sandboxed_api/util/status_macros.h"
namespace sapi {
// Common file prolog with auto-generation notice.
// Note: The includes will be adjusted by Copybara when converting to/from
// internal code. This is intentional.
// Text template arguments:
// 1. Header guard
constexpr absl::string_view kHeaderProlog =
R"(// AUTO-GENERATED by the Sandboxed API generator.
// Edits will be discarded when regenerating this file.
#ifndef %1$s
#define %1$s
#include <cstdint>
#include <type_traits>
#include "absl/base/macros.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "sandboxed_api/sandbox.h"
#include "sandboxed_api/util/status_macros.h"
#include "sandboxed_api/vars.h"
)";
constexpr absl::string_view kHeaderEpilog =
R"(
#endif // %1$s)";
// Text template arguments:
// 1. Include for embedded sandboxee objects
constexpr absl::string_view kEmbedInclude = R"(#include "%1$s_embed.h"
)";
// Text template arguments:
// 1. Namespace name
constexpr absl::string_view kNamespaceBeginTemplate =
R"(
namespace %1$s {
)";
constexpr absl::string_view kNamespaceEndTemplate =
R"(
} // namespace %1$s
)";
// Text template arguments:
// 1. Class name
// 2. Embedded object identifier
constexpr absl::string_view kEmbedClassTemplate = R"(
// Sandbox with embedded sandboxee and default policy
class %1$s : public ::sapi::Sandbox {
public:
%1$s() : ::sapi::Sandbox(%2$s_embed_create()) {}
};
)";
// Text template arguments:
// 1. Class name
constexpr absl::string_view kClassHeaderTemplate = R"(
// Sandboxed API
class %1$s {
public:
explicit %1$s(::sapi::Sandbox* sandbox) : sandbox_(sandbox) {}
ABSL_DEPRECATED("Call sandbox() instead")
::sapi::Sandbox* GetSandbox() const { return sandbox(); }
::sapi::Sandbox* sandbox() const { return sandbox_; }
)";
constexpr absl::string_view kClassFooterTemplate = R"(
private:
::sapi::Sandbox* sandbox_;
};
)";
namespace internal {
absl::StatusOr<std::string> ReformatGoogleStyle(const std::string& filename,
const std::string& code,
int column_limit) {
// Configure code style based on Google style, but enforce pointer alignment
clang::format::FormatStyle style =
clang::format::getGoogleStyle(clang::format::FormatStyle::LK_Cpp);
style.DerivePointerAlignment = false;
style.PointerAlignment = clang::format::FormatStyle::PAS_Left;
if (column_limit >= 0) {
style.ColumnLimit = column_limit;
}
clang::tooling::Replacements replacements = clang::format::reformat(
style, code, llvm::makeArrayRef(clang::tooling::Range(0, code.size())),
filename);
llvm::Expected<std::string> formatted_header =
clang::tooling::applyAllReplacements(code, replacements);
if (!formatted_header) {
return absl::InternalError(llvm::toString(formatted_header.takeError()));
}
return *formatted_header;
}
} // namespace internal
std::string GetIncludeGuard(absl::string_view filename) {
if (filename.empty()) {
static auto* bit_gen = new absl::BitGen();
return absl::StrCat(
// Copybara will transform the string. This is intentional.
"SANDBOXED_API_GENERATED_HEADER_",
absl::AsciiStrToUpper(absl::StrCat(
absl::Hex(absl::Uniform<uint64_t>(*bit_gen), absl::kZeroPad16))),
"_");
}
constexpr absl::string_view kUnderscorePrefix = "SAPI_";
std::string guard;
guard.reserve(filename.size() + kUnderscorePrefix.size() + 1);
for (auto c : filename) {
if (absl::ascii_isalpha(c)) {
guard += absl::ascii_toupper(c);
continue;
}
if (guard.empty()) {
guard = kUnderscorePrefix;
}
if (absl::ascii_isdigit(c)) {
guard += c;
} else if (guard.back() != '_') {
guard += '_';
}
}
if (!absl::EndsWith(guard, "_")) {
guard += '_';
}
return guard;
}
// Returns the namespace components of a declaration's qualified name.
std::vector<std::string> GetNamespacePath(const clang::TypeDecl* decl) {
std::vector<std::string> comps;
for (const auto* ctx = decl->getDeclContext(); ctx; ctx = ctx->getParent()) {
if (const auto* nd = llvm::dyn_cast<clang::NamespaceDecl>(ctx)) {
comps.push_back(nd->getName().str());
}
}
std::reverse(comps.begin(), comps.end());
return comps;
}
std::string PrintRecordTemplateArguments(const clang::CXXRecordDecl* record) {
const auto* template_inst_decl = record->getTemplateInstantiationPattern();
if (!template_inst_decl) {
return "";
}
const auto* template_decl = template_inst_decl->getDescribedClassTemplate();
if (!template_decl) {
return "";
}
const auto* template_params = template_decl->getTemplateParameters();
if (!template_params) {
return "";
}
std::vector<std::string> params;
params.reserve(template_params->size());
for (const auto& template_param : *template_params) {
if (const auto* p =
llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(template_param)) {
// TODO(cblichmann): These types should be included by
// CollectRelatedTypes().
params.push_back(
p->getType().getDesugaredType(record->getASTContext()).getAsString());
} else { // Also covers template template parameters
params.push_back("typename");
}
absl::StrAppend(&params.back(), " /*",
std::string(template_param->getName()), "*/");
}
return absl::StrCat("template <", absl::StrJoin(params, ", "), ">");
}
// Serializes the given Clang AST declaration back into compilable source code.
std::string PrintDecl(const clang::Decl* decl) {
std::string pretty;
llvm::raw_string_ostream os(pretty);
decl->print(os);
return os.str();
}
// Returns the spelling for a given declaration will be emitted to the final
// header. This may rewrite declarations (like converting typedefs to using,
// etc.).
std::string GetSpelling(const clang::Decl* decl) {
// TODO(cblichmann): Make types nicer
// - Rewrite typedef to using
// - Rewrite function pointers using std::add_pointer_t<>;
if (const auto* typedef_decl = llvm::dyn_cast<clang::TypedefNameDecl>(decl)) {
// Special case: anonymous enum/struct
if (auto* tag_decl = typedef_decl->getAnonDeclWithTypedefName()) {
return absl::StrCat("typedef ", PrintDecl(tag_decl), " ",
ToStringView(typedef_decl->getName()));
}
}
if (const auto* record_decl = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
if (!record_decl->isCLike()) {
// For C++ classes/structs, only emit a forward declaration.
return absl::StrCat(PrintRecordTemplateArguments(record_decl),
record_decl->isClass() ? "class " : "struct ",
ToStringView(record_decl->getName()));
}
}
return PrintDecl(decl);
}
std::string GetParamName(const clang::ParmVarDecl* decl, int index) {
if (std::string name = decl->getName().str(); !name.empty()) {
return absl::StrCat(name, "_"); // Suffix to avoid collisions
}
return absl::StrCat("unnamed", index, "_");
}
absl::StatusOr<std::string> PrintFunctionPrototypeComment(
const clang::FunctionDecl* decl) {
// TODO(cblichmann): Fix function pointers and anonymous namespace formatting
std::string out = absl::StrCat(decl->getDeclaredReturnType().getAsString(),
" ", decl->getQualifiedNameAsString(), "(");
std::string print_separator;
for (int i = 0; i < decl->getNumParams(); ++i) {
const clang::ParmVarDecl* param = decl->getParamDecl(i);
absl::StrAppend(&out, print_separator);
print_separator = ", ";
absl::StrAppend(&out, param->getType().getAsString());
if (std::string name = param->getName().str(); !name.empty()) {
absl::StrAppend(&out, " ", name);
}
}
absl::StrAppend(&out, ")");
SAPI_ASSIGN_OR_RETURN(
std::string formatted,
internal::ReformatGoogleStyle(/*filename=*/"input", out, 75));
out.clear();
for (const auto& line : absl::StrSplit(formatted, '\n')) {
absl::StrAppend(&out, "// ", line, "\n");
}
return out;
}
absl::StatusOr<std::string> EmitFunction(const clang::FunctionDecl* decl) {
std::string out;
SAPI_ASSIGN_OR_RETURN(std::string prototype,
PrintFunctionPrototypeComment(decl));
absl::StrAppend(&out, "\n", prototype);
auto function_name = ToStringView(decl->getName());
const clang::QualType return_type = decl->getDeclaredReturnType();
const bool returns_void = return_type->isVoidType();
const clang::ASTContext& context = decl->getASTContext();
// "Status<OptionalReturn> FunctionName("
absl::StrAppend(&out, MapQualTypeReturn(context, return_type), " ",
function_name, "(");
struct ParameterInfo {
clang::QualType qual;
std::string name;
};
std::vector<ParameterInfo> params;
std::string print_separator;
for (int i = 0; i < decl->getNumParams(); ++i) {
const clang::ParmVarDecl* param = decl->getParamDecl(i);
ParameterInfo& param_info = params.emplace_back();
param_info.qual = param->getType();
param_info.name = GetParamName(param, i);
absl::StrAppend(&out, print_separator);
print_separator = ", ";
absl::StrAppend(&out, MapQualTypeParameter(context, param_info.qual), " ",
param_info.name);
}
absl::StrAppend(&out, ") {\n");
absl::StrAppend(&out, MapQualType(context, return_type), " v_ret_;\n");
for (const auto& [qual, name] : params) {
if (!IsPointerOrReference(qual)) {
absl::StrAppend(&out, MapQualType(context, qual), " v_", name, "(", name,
");\n");
}
}
absl::StrAppend(&out, "\nSAPI_RETURN_IF_ERROR(sandbox_->Call(\"",
function_name, "\", &v_ret_");
for (const auto& [qual, name] : params) {
absl::StrAppend(&out, ", ", IsPointerOrReference(qual) ? "" : "&v_", name);
}
absl::StrAppend(&out, "));\nreturn ",
(returns_void ? "::absl::OkStatus()" : "v_ret_.GetValue()"),
";\n}\n");
return out;
}
absl::StatusOr<std::string> EmitHeader(
const std::vector<std::string>& functions,
const Emitter::RenderedTypesMap& rendered_types,
const GeneratorOptions& options) {
std::string out;
const std::string include_guard = GetIncludeGuard(options.out_file);
absl::StrAppendFormat(&out, kHeaderProlog, include_guard);
// When embedding the sandboxee, add embed header include
if (!options.embed_name.empty()) {
// Not using JoinPath() because even on Windows include paths use plain
// slashes.
std::string include_file(absl::StripSuffix(
absl::StrReplaceAll(options.embed_dir, {{"\\", "/"}}), "/"));
if (!include_file.empty()) {
absl::StrAppend(&include_file, "/");
}
absl::StrAppend(&include_file, options.embed_name);
absl::StrAppendFormat(&out, kEmbedInclude, include_file);
}
// If specified, wrap the generated API in a namespace
if (options.has_namespace()) {
absl::StrAppendFormat(&out, kNamespaceBeginTemplate,
options.namespace_name);
}
// Emit type dependencies
if (!rendered_types.empty()) {
absl::StrAppend(&out, "// Types this API depends on\n");
for (const auto& [ns_name, types] : rendered_types) {
if (!ns_name.empty()) {
absl::StrAppend(&out, "namespace ", ns_name, " {\n");
}
for (const auto& type : types) {
absl::StrAppend(&out, type, ";\n");
}
if (!ns_name.empty()) {
absl::StrAppend(&out, "} // namespace ", ns_name, "\n\n");
}
}
}
// Optionally emit a default sandbox that instantiates an embedded sandboxee
if (!options.embed_name.empty()) {
// TODO(cblichmann): Make the "Sandbox" suffix configurable.
absl::StrAppendFormat(
&out, kEmbedClassTemplate, absl::StrCat(options.name, "Sandbox"),
absl::StrReplaceAll(options.embed_name, {{"-", "_"}}));
}
// Emit the actual Sandboxed API
// TODO(cblichmann): Make the "Api" suffix configurable or at least optional.
absl::StrAppendFormat(&out, kClassHeaderTemplate,
absl::StrCat(options.name, "Api"));
absl::StrAppend(&out, absl::StrJoin(functions, "\n"));
absl::StrAppend(&out, kClassFooterTemplate);
// Close out the header: close namespace (if needed) and end include guard
if (options.has_namespace()) {
absl::StrAppendFormat(&out, kNamespaceEndTemplate, options.namespace_name);
}
absl::StrAppendFormat(&out, kHeaderEpilog, include_guard);
return out;
}
void Emitter::CollectType(clang::QualType qual) {
clang::TypeDecl* decl = nullptr;
if (const auto* typedef_type = qual->getAs<clang::TypedefType>()) {
decl = typedef_type->getDecl();
} else if (const auto* enum_type = qual->getAs<clang::EnumType>()) {
decl = enum_type->getDecl();
} else if (const auto* record_type = qual->getAs<clang::RecordType>()) {
decl = record_type->getDecl();
}
if (!decl) {
return;
}
const std::vector<std::string> ns_path = GetNamespacePath(decl);
std::string ns_name;
if (!ns_path.empty()) {
const auto& ns_root = ns_path.front();
// Filter out any and all declarations from the C++ standard library,
// from SAPI itself and from other well-known namespaces. This avoids
// re-declaring things like standard integer types, for example.
if (ns_root == "std" || ns_root == "__gnu_cxx" || ns_root == "sapi") {
return;
}
if (ns_root == "absl") {
// Skip types from Abseil that we already include in the generated
// header.
if (auto name = ToStringView(decl->getName());
name == "StatusCode" || name == "StatusToStringMode" ||
name == "CordMemoryAccounting") {
return;
}
}
ns_name = absl::StrJoin(ns_path, "::");
}
rendered_types_[ns_name].push_back(GetSpelling(decl));
}
void Emitter::CollectFunction(clang::FunctionDecl* decl) {
functions_.push_back(*EmitFunction(decl)); // Cannot currently fail
}
absl::StatusOr<std::string> Emitter::EmitHeader(
const GeneratorOptions& options) {
SAPI_ASSIGN_OR_RETURN(
const std::string header,
::sapi::EmitHeader(functions_, rendered_types_, options));
return internal::ReformatGoogleStyle(options.out_file, header);
}
} // namespace sapi