// 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 #include #include #include "absl/container/flat_hash_set.h" #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/status/statusor.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/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Type.h" #include "sandboxed_api/tools/clang_generator/diagnostics.h" #include "sandboxed_api/tools/clang_generator/emitter_base.h" #include "sandboxed_api/tools/clang_generator/generator.h" #include "sandboxed_api/tools/clang_generator/types.h" #include "sandboxed_api/util/status_macros.h" namespace sapi { // Common header description with auto-generation notice. constexpr absl::string_view kHeaderDescription = R"(// AUTO-GENERATED by the Sandboxed API generator. // Edits will be discarded when regenerating this file.)"; // Common header 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 kHeaderIncludes = R"( #include #include #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" )"; // Text template arguments: // 1. Include for embedded sandboxee objects constexpr absl::string_view kEmbedInclude = R"(#include "%1$s_embed.h" )"; // 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([]() { static auto* fork_client_context = new ::sapi::ForkClientContext(%2$s_embed_create()); return fork_client_context; }()) {} }; )"; // Sandboxed API class template. // 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_; } )"; // Sandboxed API class template footer. constexpr absl::string_view kClassFooterTemplate = R"( private: ::sapi::Sandbox* sandbox_; }; )"; // Returns a unique name for a parameter. If `decl` has no name, a unique name // will be generated in the form of `unnamed_`. 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, "_"); } // Returns a comment for the given function `decl` which represents the // unsandboxed function signature. absl::StatusOr PrintFunctionPrototypeComment( const clang::FunctionDecl* decl) { const clang::ASTContext& context = decl->getASTContext(); std::string out = absl::StrCat( MapQualTypeParameterForCxx(context, decl->getDeclaredReturnType()), " ", 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, MapQualTypeParameterForCxx(context, param->getType())); 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; } // Emits the given function `decl` as SAPI function with a leading comment // documenting the unsandboxed function signature. absl::StatusOr EmitFunction(const clang::FunctionDecl* decl) { const clang::QualType return_type = decl->getDeclaredReturnType(); // Skip functions returning record by value. if (return_type->isRecordType()) { return MakeStatusWithDiagnostic( decl->getBeginLoc(), absl::StatusCode::kCancelled, "Returning record by value, skipping function."); } SAPI_ASSIGN_OR_RETURN(std::string prototype, PrintFunctionPrototypeComment(decl)); std::string out; absl::StrAppend(&out, "\n", prototype); auto function_name = ToStringView(decl->getName()); const bool returns_void = return_type->isVoidType(); const clang::ASTContext& context = decl->getASTContext(); absl::StrAppend(&out, MapQualTypeReturn(context, return_type), " ", function_name, "("); struct ParameterInfo { clang::QualType qual; std::string name; }; std::vector params; // Process the function parameter list. std::string print_separator; for (int i = 0; i < decl->getNumParams(); ++i) { const clang::ParmVarDecl* param = decl->getParamDecl(i); // Skip functions with record parameters passed by value. if (param->getType()->isRecordType()) { return MakeStatusWithDiagnostic( param->getBeginLoc(), absl::StatusCode::kCancelled, absl::StrCat("Passing record parameter '", ToStringView(param->getName()), "' by value, skipping function.")); } 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"); // Declare the return value of the SAPI function. absl::StrAppend(&out, MapQualType(context, return_type), " v_ret_;\n"); // Declare the local variables for the parameters. for (const auto& [qual, name] : params) { if (!IsPointerOrReference(qual)) { absl::StrAppend(&out, MapQualType(context, qual), " v_", name, "(", name, ");\n"); } } // Call the sandboxed function. 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); } // End the sandboxed function call and return `ok` if the unsandboxed function // returns void, or else return the value of the SAPI function. absl::StrAppend(&out, "));\nreturn ", (returns_void ? "::absl::OkStatus()" : "v_ret_.GetValue()"), ";\n}\n"); return out; } // Emits the SAPI header. absl::StatusOr EmitHeader( const std::vector& function_definitions, const std::vector& rendered_types, const GeneratorOptions& options) { // Log a warning message if the number of requested functions is not equal to // the number of functions generated. if (!options.function_names.empty() && (options.function_names.size() != function_definitions.size())) { LOG(WARNING) << "Generated output has fewer functions than expected - some " "function signatures might use language features that " "SAPI does not support. For debugging, we recommend you " "compare the list of functions in your sapi_library() rule " "with the generated *.sapi.h file. Expected: " << options.function_names.size() << ", generated: " << function_definitions.size(); } std::string out; const std::string include_guard = GetIncludeGuard(options.out_file); absl::StrAppend(&out, kHeaderDescription); absl::StrAppendFormat(&out, kHeaderProlog, include_guard); absl::StrAppend(&out, kHeaderIncludes); // 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"); std::string last_ns_name = options.namespace_name; for (const RenderedType* rt : rendered_types) { const auto& [ns_name, spelling] = *rt; if (last_ns_name != ns_name) { if (!last_ns_name.empty() && last_ns_name != options.namespace_name) { absl::StrAppend(&out, "} // namespace ", last_ns_name, "\n\n"); } if (!ns_name.empty() && ns_name != options.namespace_name) { absl::StrAppend(&out, "namespace ", ns_name, " {\n"); } last_ns_name = ns_name; } absl::StrAppend(&out, spelling, ";\n"); } if (!last_ns_name.empty() && last_ns_name != options.namespace_name) { absl::StrAppend(&out, "} // namespace ", last_ns_name, "\n\n"); } } // Optionally emit a default sandbox that instantiates an embedded sandboxee if (!options.embed_name.empty()) { absl::StrAppendFormat( &out, kEmbedClassTemplate, absl::StrCat(options.name, "Sandbox"), absl::StrReplaceAll(options.embed_name, {{"-", "_"}})); } // Emit the actual Sandboxed API absl::StrAppendFormat(&out, kClassHeaderTemplate, absl::StrCat(options.name, "Api")); absl::StrAppend(&out, absl::StrJoin(function_definitions, "\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; } absl::Status Emitter::AddFunction(clang::FunctionDecl* decl) { if (rendered_functions_.insert(decl->getQualifiedNameAsString()).second) { SAPI_ASSIGN_OR_RETURN(std::string function, EmitFunction(decl)); rendered_functions_ordered_.push_back(function); } return absl::OkStatus(); } absl::StatusOr Emitter::EmitHeader( const GeneratorOptions& options) { SAPI_ASSIGN_OR_RETURN(const std::string header, ::sapi::EmitHeader(rendered_functions_ordered_, rendered_types_ordered_, options)); return internal::ReformatGoogleStyle(options.out_file, header); } } // namespace sapi