| // 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/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) { |
| // 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->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, "_"); |
| } |
| |
| std::string PrintFunctionPrototype(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, ")"); |
| return out; |
| } |
| |
| absl::StatusOr<std::string> EmitFunction(const clang::FunctionDecl* decl) { |
| std::string out; |
| absl::StrAppend(&out, "\n// ", PrintFunctionPrototype(decl), "\n"); |
| 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 { |
| decl = qual->getAsRecordDecl(); |
| } |
| 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 |