mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
728355da87
This change allows us to emit forward declarations to classes that are templated. For headers generated by the proto compiler this is sometimes necessary. Note: - This will only emit types for a single level of template instantiations. That is, template template arguments are not supported. - Typedefs only occurring in template arguments will be fully desugared and thus will not be available under their aliased name in the generated API code. This is consistent with the Python based generator (which does not emit these at all and relies on text extraction). Signed-off-by: Christian Blichmann <cblichmann@google.com>
421 lines
14 KiB
C++
421 lines
14 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
|
|
//
|
|
// http://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/string_view.h"
|
|
#include "absl/strings/strip.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DeclTemplate.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/vars.h"
|
|
#include "sandboxed_api/util/status_macros.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) {
|
|
// 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;
|
|
|
|
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->getNameAsString());
|
|
}
|
|
}
|
|
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 PrintAstDecl(const clang::Decl* decl) {
|
|
// TODO(cblichmann): Make types nicer
|
|
// - Rewrite typedef to using
|
|
// - Rewrite function pointers using std::add_pointer_t<>;
|
|
|
|
if (const auto* record = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
|
|
// For C++ classes/structs, only emit a forward declaration.
|
|
return absl::StrCat(PrintRecordTemplateArguments(record),
|
|
record->isClass() ? "class " : "struct ",
|
|
std::string(record->getName()));
|
|
}
|
|
std::string pretty;
|
|
llvm::raw_string_ostream os(pretty);
|
|
decl->print(os);
|
|
return os.str();
|
|
}
|
|
|
|
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, "_");
|
|
}
|
|
|
|
std::string PrintFunctionPrototype(const clang::FunctionDecl* decl) {
|
|
// TODO(cblichmann): Fix function pointers and anonymous namespace formatting
|
|
std::string out =
|
|
absl::StrCat(decl->getDeclaredReturnType().getAsString(), " ",
|
|
std::string(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, ")");
|
|
return out;
|
|
}
|
|
|
|
absl::StatusOr<std::string> EmitFunction(const clang::FunctionDecl* decl) {
|
|
std::string out;
|
|
absl::StrAppend(&out, "\n// ", PrintFunctionPrototype(decl), "\n");
|
|
const std::string function_name = decl->getNameAsString();
|
|
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 {
|
|
decl = qual->getAsRecordDecl();
|
|
}
|
|
if (!decl) {
|
|
return;
|
|
}
|
|
|
|
const std::vector<std::string> ns_path = GetNamespacePath(decl);
|
|
std::string ns_name;
|
|
if (!ns_path.empty()) {
|
|
if (const auto& ns_root = ns_path.front();
|
|
ns_root == "std" || ns_root == "sapi" || ns_root == "__gnu_cxx") {
|
|
// 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.
|
|
return;
|
|
}
|
|
ns_name = absl::StrCat(ns_path[0].empty() ? "" : " ",
|
|
absl::StrJoin(ns_path, "::"));
|
|
}
|
|
|
|
rendered_types_[ns_name].push_back(PrintAstDecl(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
|