mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
6d3adc45f7
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
465 lines
15 KiB
C++
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(¶ms.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
|