From 507eb00a90ce02c7f70c17da3cf8479a23a58f26 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 15 May 2020 15:44:44 +0200 Subject: [PATCH] Add sandboxee embedding - Implement `--sapi_embed_name` and `--sapi_embed_dir` flags - Do not emit full AST-serialization for C++ classes --- .../tools/clang_generator/emitter.cc | 139 ++++++++++++------ sandboxed_api/tools/clang_generator/emitter.h | 20 +++ .../tools/clang_generator/generator.h | 4 +- .../tools/clang_generator/generator_tool.cc | 2 + sandboxed_api/tools/clang_generator/types.cc | 43 +++++- sandboxed_api/tools/clang_generator/types.h | 10 +- 6 files changed, 155 insertions(+), 63 deletions(-) diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc index 7789878..fa835d7 100644 --- a/sandboxed_api/tools/clang_generator/emitter.cc +++ b/sandboxed_api/tools/clang_generator/emitter.cc @@ -17,9 +17,11 @@ #include "absl/random/random.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_join.h" #include "absl/strings/str_replace.h" +#include "absl/strings/strip.h" #include "clang/AST/DeclCXX.h" #include "sandboxed_api/tools/clang_generator/diagnostics.h" #include "sandboxed_api/util/status_macros.h" @@ -57,21 +59,13 @@ std::string GetIncludeGuard(absl::string_view filename) { return guard; } -// Returns the components of a declaration's qualified name, excluding the -// declaration itself. -sapi::StatusOr> GetQualifiedNamePath( - const clang::TypeDecl* decl) { +// Returns the namespace components of a declaration's qualified name. +std::vector GetNamespacePath(const clang::TypeDecl* decl) { std::vector comps; for (const auto* ctx = decl->getDeclContext(); ctx; ctx = ctx->getParent()) { - if (llvm::isa(ctx)) { - continue; + if (const auto* nd = llvm::dyn_cast(ctx)) { + comps.push_back(nd->getNameAsString()); } - const auto* nd = llvm::dyn_cast(ctx); - if (!nd) { - return MakeStatusWithDiagnostic(decl->getBeginLoc(), - absl::StrCat("not in a namespace decl")); - } - comps.push_back(nd->getNameAsString()); } std::reverse(comps.begin(), comps.end()); return comps; @@ -79,6 +73,15 @@ sapi::StatusOr> GetQualifiedNamePath( // 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(decl)) { + // For C++ classes/structs, only emit a forward declaration. + return absl::StrCat(record->isClass() ? "class " : "struct ", + std::string(record->getName())); + } std::string pretty; llvm::raw_string_ostream os(pretty); decl->print(os); @@ -120,9 +123,11 @@ sapi::StatusOr EmitFunction(const clang::FunctionDecl* decl) { const clang::QualType return_type = decl->getDeclaredReturnType(); const bool returns_void = return_type->isVoidType(); + const clang::ASTContext& context = decl->getASTContext(); + // "Status FunctionName(" - absl::StrAppend(&out, MapQualTypeReturn(return_type), " ", function_name, - "("); + absl::StrAppend(&out, MapQualTypeReturn(context, return_type), " ", + function_name, "("); struct ParameterInfo { clang::QualType qual; @@ -134,20 +139,22 @@ sapi::StatusOr EmitFunction(const clang::FunctionDecl* decl) { for (int i = 0; i < decl->getNumParams(); ++i) { const clang::ParmVarDecl* param = decl->getParamDecl(i); - ParameterInfo& pi = params.emplace_back(); - pi.qual = param->getType(); - pi.name = GetParamName(param, 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(pi.qual), " ", pi.name); + absl::StrAppend(&out, MapQualTypeParameter(context, param_info.qual), " ", + param_info.name); } absl::StrAppend(&out, ") {\n"); - absl::StrAppend(&out, MapQualType(return_type), " v_ret_;\n"); + absl::StrAppend(&out, MapQualType(context, return_type), " v_ret_;\n"); for (const auto& [qual, name] : params) { if (!IsPointerOrReference(qual)) { - absl::StrAppend(&out, MapQualType(qual), " v_", name, "(", name, ");\n"); + absl::StrAppend(&out, MapQualType(context, qual), " v_", name, "(", name, + ");\n"); } } absl::StrAppend(&out, "\nSAPI_RETURN_IF_ERROR(sandbox_->Call(\"", @@ -167,44 +174,77 @@ sapi::StatusOr EmitHeader( 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); } - { - std::string out_types = "// Types this API depends on\n"; - bool added_dependent_types = false; - for (const clang::QualType& qual : types) { - clang::TypeDecl* decl = nullptr; - if (const auto* typedef_type = qual->getAs()) { - decl = typedef_type->getDecl(); - } else if (const auto* enum_type = qual->getAs()) { - decl = enum_type->getDecl(); - } else { - decl = qual->getAsRecordDecl(); - } - if (decl) { - SAPI_ASSIGN_OR_RETURN(auto ns, GetQualifiedNamePath(decl)); - if (!ns.empty()) { - absl::StrAppend(&out_types, "namespace", (ns[0].empty() ? "" : " "), - absl::StrJoin(ns, "::"), " {\n"); - } - // TODO(cblichmann): Make types nicer - // - Rewrite typedef to using - // - Rewrite function pointers using std::add_pointer_t<>; - absl::StrAppend(&out_types, PrintAstDecl(decl), ";"); - if (!ns.empty()) { - absl::StrAppend(&out_types, "\n}"); - } - absl::StrAppend(&out_types, "\n"); - added_dependent_types = true; + // Emit type dependencies + // TODO(cblichmann): Coalesce namespaces + std::string out_types = "// Types this API depends on\n"; + bool added_types = false; + for (const clang::QualType& qual : types) { + clang::TypeDecl* decl = nullptr; + if (const auto* typedef_type = qual->getAs()) { + decl = typedef_type->getDecl(); + } else if (const auto* enum_type = qual->getAs()) { + decl = enum_type->getDecl(); + } else { + decl = qual->getAsRecordDecl(); + } + if (!decl) { + continue; + } + + const std::vector ns_path = GetNamespacePath(decl); + std::string nested_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. + continue; } + nested_ns_name = absl::StrCat(ns_path[0].empty() ? "" : " ", + absl::StrJoin(ns_path, "::")); + absl::StrAppend(&out_types, "namespace", nested_ns_name, " {\n"); } - if (added_dependent_types) { - absl::StrAppend(&out, out_types); + absl::StrAppend(&out_types, PrintAstDecl(decl), ";"); + if (!ns_path.empty()) { + absl::StrAppend(&out_types, "\n} // namespace", nested_ns_name); } + absl::StrAppend(&out_types, "\n"); + added_types = true; } + if (added_types) { + absl::StrAppend(&out, out_types); + } + + // 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")); @@ -215,6 +255,7 @@ sapi::StatusOr EmitHeader( } 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); } diff --git a/sandboxed_api/tools/clang_generator/emitter.h b/sandboxed_api/tools/clang_generator/emitter.h index f8d5e56..84cebdf 100644 --- a/sandboxed_api/tools/clang_generator/emitter.h +++ b/sandboxed_api/tools/clang_generator/emitter.h @@ -43,6 +43,8 @@ sapi::StatusOr EmitHeader( // 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 inline constexpr absl::string_view kHeaderProlog = R"(// AUTO-GENERATED by the Sandboxed API generator. // Edits will be discarded when regenerating this file. @@ -65,6 +67,12 @@ inline constexpr absl::string_view kHeaderEpilog = R"( #endif // %1$s)"; +// Text template arguments: +// 1. Include for embedded sandboxee objects +inline constexpr absl::string_view kEmbedInclude = R"(#include "%1$s_embed.h" + +)"; + // Text template arguments: // 1. Namespace name inline constexpr absl::string_view kNamespaceBeginTemplate = @@ -77,6 +85,18 @@ R"( } // namespace %1$s )"; +// Text template arguments: +// 1. Class name +// 2. Embedded object identifier +inline 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 inline constexpr absl::string_view kClassHeaderTemplate = R"( diff --git a/sandboxed_api/tools/clang_generator/generator.h b/sandboxed_api/tools/clang_generator/generator.h index 3167c76..29a05d2 100644 --- a/sandboxed_api/tools/clang_generator/generator.h +++ b/sandboxed_api/tools/clang_generator/generator.h @@ -46,6 +46,8 @@ struct GeneratorOptions { std::string name; // Name of the Sandboxed API std::string namespace_name; // Namespace to wrap the SAPI in std::string out_file; // Output path of the generated header + std::string embed_dir; // Directory with embedded includes + std::string embed_name; // Identifier of the embed object }; class GeneratorASTVisitor @@ -92,8 +94,6 @@ class GeneratorAction : public clang::ASTFrontendAction { : options_(options) {} private: - friend class GeneratorFactory; - std::unique_ptr CreateASTConsumer( clang::CompilerInstance&, llvm::StringRef in_file) override { return absl::make_unique(std::string(in_file), diff --git a/sandboxed_api/tools/clang_generator/generator_tool.cc b/sandboxed_api/tools/clang_generator/generator_tool.cc index 377e741..5d5caab 100644 --- a/sandboxed_api/tools/clang_generator/generator_tool.cc +++ b/sandboxed_api/tools/clang_generator/generator_tool.cc @@ -83,6 +83,8 @@ GeneratorOptions GeneratorOptionsFromFlags() { options.name = *g_sapi_name; options.namespace_name = *g_sapi_ns; options.out_file = *g_sapi_out; + options.embed_dir = *g_sapi_embed_dir; + options.embed_name = *g_sapi_embed_name; return options; } diff --git a/sandboxed_api/tools/clang_generator/types.cc b/sandboxed_api/tools/clang_generator/types.cc index bc68cc1..11795a3 100644 --- a/sandboxed_api/tools/clang_generator/types.cc +++ b/sandboxed_api/tools/clang_generator/types.cc @@ -78,7 +78,24 @@ void GatherRelatedTypes(const clang::ASTContext& context, clang::QualType qual, } } -std::string MapQualType(clang::QualType qual) { +namespace { + +// Removes "const" from a qualified type if it denotes a pointer or reference +// type. +clang::QualType MaybeRemoveConst(const clang::ASTContext& context, + clang::QualType qual) { + if (IsPointerOrReference(qual)) { + clang::QualType pointee_qual = qual->getPointeeType(); + pointee_qual.removeLocalConst(); + qual = context.getPointerType(pointee_qual); + } + return qual; +} + +} // namespace + +std::string MapQualType(const clang::ASTContext& context, + clang::QualType qual) { if (const auto* builtin = qual->getAs()) { switch (builtin->getKind()) { case clang::BuiltinType::Void: @@ -156,12 +173,18 @@ std::string MapQualType(clang::QualType qual) { } else if (const auto* enum_type = qual->getAs()) { return absl::StrCat("::sapi::v::IntBase<", enum_type->getDecl()->getQualifiedNameAsString(), ">"); + } else if (IsPointerOrReference(qual)) { + // Remove "const" qualifier from a pointer or reference type's pointee, as + // e.g. const pointers do not work well with SAPI. + return absl::StrCat("::sapi::v::Reg<", + MaybeRemoveConst(context, qual).getAsString(), ">"); } - // Best-effort mapping to "int" - return "::sapi::v::Int"; + // Best-effort mapping to "int", leave a comment. + return absl::StrCat("::sapi::v::Int /* aka '", qual.getAsString(), "' */"); } -std::string MapQualTypeParameter(clang::QualType qual) { +std::string MapQualTypeParameter(const clang::ASTContext& /*context*/, + clang::QualType qual) { // TODO(cblichmann): Define additional mappings, as appropriate // _Bool -> bool // unsigned long long -> uint64_t (where applicable) @@ -169,10 +192,14 @@ std::string MapQualTypeParameter(clang::QualType qual) { return IsPointerOrReference(qual) ? "::sapi::v::Ptr*" : qual.getAsString(); } -std::string MapQualTypeReturn(clang::QualType qual) { - return qual->isVoidType() ? "absl::Status" - : absl::StrCat("::sapi::StatusOr<", - MapQualTypeParameter(qual), ">"); +std::string MapQualTypeReturn(const clang::ASTContext& context, + clang::QualType qual) { + if (qual->isVoidType()) { + return "absl::Status"; + } + // Remove const qualifier like in MapQualType(). + return absl::StrCat("::sapi::StatusOr<", + MaybeRemoveConst(context, qual).getAsString(), ">"); } } // namespace sapi diff --git a/sandboxed_api/tools/clang_generator/types.h b/sandboxed_api/tools/clang_generator/types.h index c5203bb..bb4a1a0 100644 --- a/sandboxed_api/tools/clang_generator/types.h +++ b/sandboxed_api/tools/clang_generator/types.h @@ -60,17 +60,19 @@ void GatherRelatedTypes(const clang::ASTContext& context, clang::QualType qual, // Maps a qualified type to a fully qualified SAPI-compatible type name. This // is used for the generated code that invokes the actual function call RPC. // If no mapping can be found, "int" is assumed. -std::string MapQualType(clang::QualType qual); +std::string MapQualType(const clang::ASTContext& context, clang::QualType qual); // Maps a qualified type used as a function parameter to a type name compatible // with the generated Sandboxed API. -std::string MapQualTypeParameter(clang::QualType qual); +std::string MapQualTypeParameter(const clang::ASTContext& context, + clang::QualType qual); // Maps a qualified type used as a function return type to a type name -// compatible with the generated Sandboxed API. Uses MapQualTypeParameter() and +// compatible with the generated Sandboxed API. Uses MapQualTypeParameter() and // wraps the type in a ::sapi::StatusOr<> if qual is non-void. Otherwise returns // absl::Status. -std::string MapQualTypeReturn(clang::QualType qual); +std::string MapQualTypeReturn(const clang::ASTContext& context, + clang::QualType qual); } // namespace sapi