diff --git a/.gitignore b/.gitignore index 411d1ab..b20ccc9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,16 @@ .DS_Store # Build files bazel-* +# Local CMake build dir build/ # Editor backup files *.swp *~ +# Compiled Python files and cache +*.py[co] +__py_cache__/ # IDE files +.clangd/ .vscode/ .idea compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 6295643..f35c9c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,22 @@ include(SapiDeps) include(SapiUtil) include(SapiBuildDefs) +if(CMAKE_GENERATOR MATCHES "Ninja") + file(WRITE "${CMAKE_BINARY_DIR}/GNUMakeRulesOverwrite.cmake" + "STRING(REPLACE \"-MD\" \"-MMD\" CMAKE_DEPFILE_FLAGS_C \"\${CMAKE_DEPFILE_FLAGS_C}\")\n" + "STRING(REPLACE \"-MD\" \"-MMD\" CMAKE_DEPFILE_FLAGS_CXX \"\${CMAKE_DEPFILE_FLAGS_CXX}\")\n" + ) + set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_BINARY_DIR}/GNUMakeRulesOverwrite.cmake" CACHE INTERNAL "") +endif() + +if (SAPI_FORCE_COLOR_OUTPUT) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # GCC + add_compile_options(-fdiagnostics-color=always) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # Clang or Apple Clang + add_compile_options(-fcolor-diagnostics) + endif() +endif() + # Make Bazel-style includes work configure_file(cmake/libcap_capability.h.in libcap/include/sys/capability.h diff --git a/cmake/SapiBuildDefs.cmake b/cmake/SapiBuildDefs.cmake index f3938a6..5e75e59 100644 --- a/cmake/SapiBuildDefs.cmake +++ b/cmake/SapiBuildDefs.cmake @@ -128,25 +128,42 @@ function(add_sapi_library) get_filename_component(src "${src}" ABSOLUTE) list(APPEND _sapi_full_inputs "${src}") endforeach() - list_join(_sapi_full_inputs "," _sapi_full_inputs) if(NOT _sapi_NOEMBED) set(_sapi_embed_dir "${CMAKE_CURRENT_BINARY_DIR}") set(_sapi_embed_name "${_sapi_NAME}") endif() - add_custom_command( - OUTPUT "${_sapi_gen_header}" - COMMAND "${SAPI_PYTHON3_EXECUTABLE}" -B - "${SAPI_SOURCE_DIR}/sandboxed_api/tools/generator2/sapi_generator.py" - "--sapi_name=${_sapi_LIBRARY_NAME}" - "--sapi_out=${_sapi_gen_header}" - "--sapi_embed_dir=${_sapi_embed_dir}" - "--sapi_embed_name=${_sapi_embed_name}" - "--sapi_functions=${_sapi_funcs}" - "--sapi_ns=${_sapi_NAMESPACE}" - # TODO(cblichmann): Implement sapi_isystem - "--sapi_in=${_sapi_full_inputs}" - COMMENT "Generating interface" - ) + # TODO(cblichmann): Implement sapi_isystem + if(SAPI_ENABLE_GENERATOR) + add_custom_command( + OUTPUT "${_sapi_gen_header}" + COMMAND sapi_generator_tool + "--sapi_name=${_sapi_LIBRARY_NAME}" + "--sapi_out=${_sapi_gen_header}" + "--sapi_embed_dir=${_sapi_embed_dir}" + "--sapi_embed_name=${_sapi_embed_name}" + "--sapi_functions=${_sapi_funcs}" + "--sapi_ns=${_sapi_NAMESPACE}" + ${_sapi_full_inputs} + COMMENT "Generating interface" + DEPENDS ${_sapi_INPUTS} + VERBATIM + ) + else() + list_join(_sapi_full_inputs "," _sapi_full_inputs) + add_custom_command( + OUTPUT "${_sapi_gen_header}" + COMMAND "${SAPI_PYTHON3_EXECUTABLE}" -B + "${SAPI_SOURCE_DIR}/sandboxed_api/tools/generator2/sapi_generator.py" + "--sapi_name=${_sapi_LIBRARY_NAME}" + "--sapi_out=${_sapi_gen_header}" + "--sapi_embed_dir=${_sapi_embed_dir}" + "--sapi_embed_name=${_sapi_embed_name}" + "--sapi_functions=${_sapi_funcs}" + "--sapi_ns=${_sapi_NAMESPACE}" + "--sapi_in=${_sapi_full_inputs}" + COMMENT "Generating interface" + ) + endif() # Library with the interface if(NOT _sapi_SOURCES) diff --git a/cmake/SapiDeps.cmake b/cmake/SapiDeps.cmake index 3dd0a97..f7805b8 100644 --- a/cmake/SapiDeps.cmake +++ b/cmake/SapiDeps.cmake @@ -89,10 +89,12 @@ if(SAPI_ENABLE_EXAMPLES) endif() endif() -# Find Python 3 and add its location to the cache so that its available in -# the add_sapi_library() macro in embedding projects. -find_package(Python3 COMPONENTS Interpreter REQUIRED) -set(SAPI_PYTHON3_EXECUTABLE "${Python3_EXECUTABLE}" CACHE INTERNAL "" FORCE) +if(NOT SAPI_ENABLE_GENERATOR) + # Find Python 3 and add its location to the cache so that its available in + # the add_sapi_library() macro in embedding projects. + find_package(Python3 COMPONENTS Interpreter REQUIRED) + set(SAPI_PYTHON3_EXECUTABLE "${Python3_EXECUTABLE}" CACHE INTERNAL "" FORCE) +endif() # Undo global change set(CMAKE_FIND_LIBRARY_SUFFIXES ${_sapi_saved_CMAKE_FIND_LIBRARY_SUFFIXES}) diff --git a/cmake/SapiOptions.cmake b/cmake/SapiOptions.cmake index 1414abe..2b242cf 100644 --- a/cmake/SapiOptions.cmake +++ b/cmake/SapiOptions.cmake @@ -34,3 +34,6 @@ option(SAPI_ENABLE_EXAMPLES "Build example code" ON) option(SAPI_DOWNLOAD_ZLIB "Download zlib at config time (only if SAPI_ENABLE_EXAMPLES is set)" ON) option(SAPI_ENABLE_TESTS "Build unit tests" ON) +option(SAPI_ENABLE_GENERATOR "Build Clang based code generator from source" OFF) + +option(SAPI_FORCE_COLOR_OUTPUT "Force colored compiler diagnostics when using Ninja" ON) diff --git a/sandboxed_api/CMakeLists.txt b/sandboxed_api/CMakeLists.txt index 3ac74aa..3fe283d 100644 --- a/sandboxed_api/CMakeLists.txt +++ b/sandboxed_api/CMakeLists.txt @@ -13,9 +13,12 @@ # limitations under the License. add_subdirectory(bazel) # For filewrapper -add_subdirectory(examples) add_subdirectory(sandbox2) add_subdirectory(util) +if(SAPI_ENABLE_GENERATOR) + add_subdirectory(tools/clang_generator) +endif() +add_subdirectory(examples) # sandboxed_api:proto_arg sapi_protobuf_generate_cpp(_sapi_proto_arg_pb_cc _sapi_proto_arg_pb_h diff --git a/sandboxed_api/sandbox2/examples/zlib/CMakeLists.txt b/sandboxed_api/sandbox2/examples/zlib/CMakeLists.txt index 199a393..6cd41ab 100644 --- a/sandboxed_api/sandbox2/examples/zlib/CMakeLists.txt +++ b/sandboxed_api/sandbox2/examples/zlib/CMakeLists.txt @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +find_package(ZLIB REQUIRED) + # sandboxed_api/sandbox2/examples/zlib:zpipe_sandbox add_executable(sandbox2_zpipe_sandbox zpipe_sandbox.cc diff --git a/sandboxed_api/sandbox2/util/fileops.cc b/sandboxed_api/sandbox2/util/fileops.cc index 8dbc51a..2132503 100644 --- a/sandboxed_api/sandbox2/util/fileops.cc +++ b/sandboxed_api/sandbox2/util/fileops.cc @@ -48,8 +48,8 @@ int FDCloser::Release() { bool GetCWD(std::string* result) { // Calling getcwd() with a nullptr buffer is a commonly implemented extension. - std::unique_ptr cwd{getcwd(nullptr, 0), - [](char* p) { free(p); }}; + std::unique_ptr cwd(getcwd(nullptr, 0), + [](char* p) { free(p); }); if (!cwd) { return false; } @@ -57,6 +57,12 @@ bool GetCWD(std::string* result) { return true; } +std::string GetCWD() { + std::string cwd; + GetCWD(&cwd); + return cwd; +} + // Makes a path absolute with respect to base. Returns true on success. Result // may be an alias of base or filename. bool MakeAbsolute(const std::string& filename, const std::string& base, diff --git a/sandboxed_api/sandbox2/util/fileops.h b/sandboxed_api/sandbox2/util/fileops.h index f8be08a..9d03fd1 100644 --- a/sandboxed_api/sandbox2/util/fileops.h +++ b/sandboxed_api/sandbox2/util/fileops.h @@ -42,6 +42,10 @@ class FDCloser { int fd_; }; +// Returns the current working directory. On error, returns an empty string. Use +// errno/GetLastError() to check the root cause in that case. +std::string GetCWD(); + // Returns the target of a symlink. Returns an empty string on failure. std::string ReadLink(const std::string& filename); diff --git a/sandboxed_api/tools/clang_generator/CMakeLists.txt b/sandboxed_api/tools/clang_generator/CMakeLists.txt new file mode 100644 index 0000000..bb6c68f --- /dev/null +++ b/sandboxed_api/tools/clang_generator/CMakeLists.txt @@ -0,0 +1,61 @@ +# 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. + +# Minimum supported: LLVM 9 +find_package(LLVM REQUIRED) +find_package(Clang REQUIRED) + +add_library(sapi_generator + diagnostics.cc + diagnostics.h + emitter.h + emitter.cc + generator.h + generator.cc + types.h + types.cc +) +add_library(sapi::generator ALIAS sapi_generator) +target_compile_definitions(sapi_generator PUBLIC + ${LLVM_DEFINITIONS} +) +target_include_directories(sapi_generator PUBLIC + ${LLVM_INCLUDE_DIRS} +) +llvm_map_components_to_libnames(_sapi_generator_llvm_libs + core +) +target_link_libraries(sapi_generator PUBLIC + sapi::base + absl::flat_hash_set + absl::memory + absl::random_random + absl::status + absl::strings + clangFormat + clangFrontendTool + clangTooling + sandbox2::fileops + sapi::status + ${_sapi_generator_llvm_libs} +) + +add_executable(sapi_generator_tool + generator_tool.cc +) +target_link_libraries(sapi_generator_tool PRIVATE + sapi::base + sandbox2::fileops + sapi::generator +) \ No newline at end of file diff --git a/sandboxed_api/tools/clang_generator/diagnostics.cc b/sandboxed_api/tools/clang_generator/diagnostics.cc new file mode 100644 index 0000000..62efbe7 --- /dev/null +++ b/sandboxed_api/tools/clang_generator/diagnostics.cc @@ -0,0 +1,56 @@ +// 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/diagnostics.h" + +#include "absl/strings/cord.h" + +namespace sapi { + +constexpr absl::string_view kSapiStatusPayload = + "https://github.com/google/sandboxed-api"; + +absl::Status MakeStatusWithDiagnostic(clang::SourceLocation loc, + absl::string_view message) { + absl::Status status = absl::UnknownError(message); + absl::Cord payload; + uint64_t raw_loc = loc.getRawEncoding(); + payload.Append( + absl::string_view(reinterpret_cast(&raw_loc), sizeof(raw_loc))); + status.SetPayload(kSapiStatusPayload, std::move(payload)); + return status; +} + +absl::optional GetDiagnosticLocationFromStatus( + const absl::Status& status) { + if (auto payload = + status.GetPayload(kSapiStatusPayload).value_or(absl::Cord()); + payload.size() == sizeof(uint64_t)) { + return clang::SourceLocation::getFromRawEncoding( + *reinterpret_cast(payload.Flatten().data())); + } + return absl::nullopt; +} + +clang::DiagnosticBuilder ReportFatalError(clang::DiagnosticsEngine& de, + clang::SourceLocation loc, + absl::string_view message) { + clang::DiagnosticBuilder builder = + de.Report(loc, de.getCustomDiagID(clang::DiagnosticsEngine::Fatal, + "header generation failed: %0")); + builder.AddString(llvm::StringRef(message.data(), message.size())); + return builder; +} + +} // namespace sapi diff --git a/sandboxed_api/tools/clang_generator/diagnostics.h b/sandboxed_api/tools/clang_generator/diagnostics.h new file mode 100644 index 0000000..6e16a0d --- /dev/null +++ b/sandboxed_api/tools/clang_generator/diagnostics.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef SANDBOXED_API_TOOLS_CLANG_GENERATOR_DIAGNOSTICS_H_ +#define SANDBOXED_API_TOOLS_CLANG_GENERATOR_DIAGNOSTICS_H_ + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" + +namespace sapi { + +// Returns a new UNKNOWN status with a payload that encodes the specified Clang +// source location. +absl::Status MakeStatusWithDiagnostic(clang::SourceLocation loc, + absl::string_view message); + +// Extracts the Clang source location encoded in a status payload. +absl::optional GetDiagnosticLocationFromStatus( + const absl::Status& status); + +clang::DiagnosticBuilder ReportFatalError(clang::DiagnosticsEngine& de, + clang::SourceLocation loc, + absl::string_view message); +} // namespace sapi + +#endif // SANDBOXED_API_TOOLS_CLANG_GENERATOR_DIAGNOSTICS_H_ diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc new file mode 100644 index 0000000..7789878 --- /dev/null +++ b/sandboxed_api/tools/clang_generator/emitter.cc @@ -0,0 +1,225 @@ +// 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/strings/ascii.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_replace.h" +#include "clang/AST/DeclCXX.h" +#include "sandboxed_api/tools/clang_generator/diagnostics.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sapi { + +std::string GetIncludeGuard(absl::string_view filename) { + if (filename.empty()) { + static auto* bit_gen = new absl::BitGen(); + using Char = std::make_unsigned_t; + constexpr int kRandomIdLen = 8; + std::string random_id; + random_id.reserve(kRandomIdLen); + for (int i = 0; i < kRandomIdLen; ++i) { + random_id += static_cast( + absl::Uniform(*bit_gen)); + } + return absl::StrCat( + "SANDBOXED_API_GENERATED_HEADER_", + absl::AsciiStrToUpper(absl::BytesToHexString(random_id)), "_"); + } + + std::string guard; + guard.reserve(filename.size() + 2); + for (auto c : filename) { + if (absl::ascii_isdigit(c) && !guard.empty()) { + guard += c; + } else if (absl::ascii_isalpha(c)) { + guard += absl::ascii_toupper(c); + } else { + guard += '_'; + } + } + guard += '_'; + return guard; +} + +// Returns the components of a declaration's qualified name, excluding the +// declaration itself. +sapi::StatusOr> GetQualifiedNamePath( + const clang::TypeDecl* decl) { + std::vector comps; + for (const auto* ctx = decl->getDeclContext(); ctx; ctx = ctx->getParent()) { + if (llvm::isa(ctx)) { + continue; + } + 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; +} + +// Serializes the given Clang AST declaration back into compilable source code. +std::string PrintAstDecl(const clang::Decl* decl) { + 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(); !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(); !name.empty()) { + absl::StrAppend(&out, " ", name); + } + } + absl::StrAppend(&out, ")"); + return out; +} + +sapi::StatusOr 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(); + + // "Status FunctionName(" + absl::StrAppend(&out, MapQualTypeReturn(return_type), " ", function_name, + "("); + + struct ParameterInfo { + clang::QualType qual; + std::string name; + }; + std::vector params; + + std::string print_separator; + 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); + + absl::StrAppend(&out, print_separator); + print_separator = ", "; + absl::StrAppend(&out, MapQualTypeParameter(pi.qual), " ", pi.name); + } + + absl::StrAppend(&out, ") {\n"); + absl::StrAppend(&out, MapQualType(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, "\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; +} + +sapi::StatusOr EmitHeader( + std::vector functions, const QualTypeSet& types, + const GeneratorOptions& options) { + std::string out; + const std::string include_guard = GetIncludeGuard(options.out_file); + absl::StrAppendFormat(&out, kHeaderProlog, include_guard); + 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; + } + } + if (added_dependent_types) { + absl::StrAppend(&out, out_types); + } + } + // TODO(cblichmann): Make the "Api" suffix configurable or at least optional. + absl::StrAppendFormat(&out, kClassHeaderTemplate, + absl::StrCat(options.name, "Api")); + std::string out_func; + for (const clang::FunctionDecl* decl : functions) { + SAPI_ASSIGN_OR_RETURN(out_func, EmitFunction(decl)); + absl::StrAppend(&out, out_func); + } + absl::StrAppend(&out, kClassFooterTemplate); + + if (options.has_namespace()) { + absl::StrAppendFormat(&out, kNamespaceEndTemplate, options.namespace_name); + } + absl::StrAppendFormat(&out, kHeaderEpilog, include_guard); + return out; +} + +} // namespace sapi \ No newline at end of file diff --git a/sandboxed_api/tools/clang_generator/emitter.h b/sandboxed_api/tools/clang_generator/emitter.h new file mode 100644 index 0000000..f8d5e56 --- /dev/null +++ b/sandboxed_api/tools/clang_generator/emitter.h @@ -0,0 +1,101 @@ +// 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. + +#ifndef SANDBOXED_API_TOOLS_CLANG_GENERATOR_EMITTER_H_ +#define SANDBOXED_API_TOOLS_CLANG_GENERATOR_EMITTER_H_ + +#include + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Type.h" +#include "sandboxed_api/tools/clang_generator/generator.h" +#include "sandboxed_api/tools/clang_generator/types.h" +#include "sandboxed_api/util/statusor.h" + +namespace sapi { + +// Constructs an include guard name for the given filename. The name is of the +// same for as the include guards in this project. +// For example, +// sandboxed_api/examples/zlib/zlib-sapi.sapi.h +// will be mapped to +// SANDBOXED_API_EXAMPLES_ZLIB_ZLIB_SAPI_SAPI_H_ +std::string GetIncludeGuard(absl::string_view filename); + +// Outputs a formatted header for a list of functions and their related types. +sapi::StatusOr EmitHeader( + std::vector functions, const QualTypeSet& types, + const GeneratorOptions& options); + +// Common file prolog with auto-generation notice. +// Note: The includes will be adjusted by Copybara when converting to/from +// internal code. This is intentional. +inline 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 +#include + +#include "absl/base/macros.h" +#include "absl/status/status.h" +#include "sandboxed_api/sandbox.h" +#include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" +#include "sandboxed_api/vars.h" + +)"; +inline constexpr absl::string_view kHeaderEpilog = +R"( +#endif // %1$s)"; + +// Text template arguments: +// 1. Namespace name +inline constexpr absl::string_view kNamespaceBeginTemplate = +R"( +namespace %1$s { + +)"; +inline constexpr absl::string_view kNamespaceEndTemplate = +R"( +} // namespace %1$s +)"; + +// Text template arguments: +// 1. Class name +inline 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_; } +)"; + +inline constexpr absl::string_view kClassFooterTemplate = R"( + private: + ::sapi::Sandbox* sandbox_; +}; +)"; + +} // namespace sapi + +#endif // SANDBOXED_API_TOOLS_CLANG_GENERATOR_EMITTER_H_ \ No newline at end of file diff --git a/sandboxed_api/tools/clang_generator/generator.cc b/sandboxed_api/tools/clang_generator/generator.cc new file mode 100644 index 0000000..49349b6 --- /dev/null +++ b/sandboxed_api/tools/clang_generator/generator.cc @@ -0,0 +1,132 @@ +// 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/generator.h" + +#include +#include + +#include "absl/status/status.h" +#include "clang/Format/Format.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/tools/clang_generator/diagnostics.h" +#include "sandboxed_api/tools/clang_generator/emitter.h" +#include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" + +namespace sapi { +namespace { + +// Replaces the file extension of a path name. +std::string ReplaceFileExtension(absl::string_view path, + absl::string_view new_extension) { + auto last_slash = path.find_last_of('/'); + if (last_slash == absl::string_view::npos) { + last_slash = 0; + } + auto pos = path.substr(last_slash).find_last_of("."); + if (pos != absl::string_view::npos) { + pos += last_slash; + } + return absl::StrCat(path.substr(0, pos), new_extension); +} + +std::string GetOutputFilename(absl::string_view source_file) { + return ReplaceFileExtension(source_file, ".sapi.h"); +} + +inline absl::string_view ToStringView(llvm::StringRef ref) { + return absl::string_view(ref.data(), ref.size()); +} + +} // namespace + +bool GeneratorASTVisitor::VisitFunctionDecl(clang::FunctionDecl* decl) { + clang::ASTContext& context = decl->getASTContext(); + if (!decl->isCXXClassMember() && // Skip classes + decl->isExternC() && // Skip non external functions + !decl->isTemplated() && // Skip function templates + // Process either all function or just the requested ones + (options_->function_names.empty() || + options_->function_names.count(ToStringView(decl->getName())) > 0)) { + functions_.push_back(decl); + GatherRelatedTypes(context, decl->getDeclaredReturnType(), &types_); + for (const clang::ParmVarDecl* param : decl->parameters()) { + GatherRelatedTypes(context, param->getType(), &types_); + } + } + return true; +} + +namespace internal { + +sapi::StatusOr 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 formatted_header = + clang::tooling::applyAllReplacements(code, replacements); + if (!formatted_header) { + return absl::InternalError(llvm::toString(formatted_header.takeError())); + } + return *formatted_header; +} + +} // namespace internal + +absl::Status GeneratorASTConsumer::GenerateAndSaveHeader() { + const std::string out_file = + options_->out_file.empty() ? GetOutputFilename(in_file_) + : sandbox2::file_util::fileops::MakeAbsolute( + options_->out_file, options_->work_dir); + + SAPI_ASSIGN_OR_RETURN( + const std::string header, + EmitHeader(visitor_.functions_, visitor_.types_, *options_)); + SAPI_ASSIGN_OR_RETURN(const std::string formatted_header, + internal::ReformatGoogleStyle(in_file_, header)); + + std::ofstream os(out_file, std::ios::out | std::ios::trunc); + os << formatted_header; + if (!os) { + return absl::UnknownError("I/O error"); + } + return absl::OkStatus(); +} + +void GeneratorASTConsumer::HandleTranslationUnit(clang::ASTContext& context) { + absl::Status status; + if (!visitor_.TraverseDecl(context.getTranslationUnitDecl())) { + status = absl::InternalError("AST traversal exited early"); + } else { + status = GenerateAndSaveHeader(); + } + + if (!status.ok()) { + ReportFatalError(context.getDiagnostics(), + GetDiagnosticLocationFromStatus(status).value_or( + context.getTranslationUnitDecl()->getBeginLoc()), + status.message()); + } +} + +} // namespace sapi \ No newline at end of file diff --git a/sandboxed_api/tools/clang_generator/generator.h b/sandboxed_api/tools/clang_generator/generator.h new file mode 100644 index 0000000..3167c76 --- /dev/null +++ b/sandboxed_api/tools/clang_generator/generator.h @@ -0,0 +1,123 @@ +// 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. + +#ifndef SANDBOXED_API_TOOLS_CLANG_GENERATOR_GENERATOR_H_ +#define SANDBOXED_API_TOOLS_CLANG_GENERATOR_GENERATOR_H_ + +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Tooling.h" +#include "sandboxed_api/tools/clang_generator/types.h" +#include "sandboxed_api/util/statusor.h" + +namespace sapi { + +struct GeneratorOptions { + template + GeneratorOptions& set_function_names(const ContainerT& value) { + function_names.clear(); + function_names.insert(std::begin(value), std::end(value)); + return *this; + } + + bool has_namespace() const { return !namespace_name.empty(); } + + absl::flat_hash_set function_names; + + // Output options + std::string work_dir; + 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 +}; + +class GeneratorASTVisitor + : public clang::RecursiveASTVisitor { + public: + bool VisitFunctionDecl(clang::FunctionDecl* decl); + + private: + friend class GeneratorASTConsumer; + const GeneratorOptions* options_ = nullptr; + + std::vector functions_; + QualTypeSet types_; +}; + +namespace internal { + +sapi::StatusOr ReformatGoogleStyle(const std::string& filename, + const std::string& code); + +} // namespace internal + +class GeneratorASTConsumer : public clang::ASTConsumer { + public: + GeneratorASTConsumer(std::string in_file, const GeneratorOptions* options) + : in_file_(std::move(in_file)), options_(options) { + visitor_.options_ = options_; + } + + private: + void HandleTranslationUnit(clang::ASTContext& context) override; + + absl::Status GenerateAndSaveHeader(); + + std::string in_file_; + const GeneratorOptions* options_; + + GeneratorASTVisitor visitor_; +}; + +class GeneratorAction : public clang::ASTFrontendAction { + public: + explicit GeneratorAction(const GeneratorOptions* options) + : 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), + options_); + } + + bool hasCodeCompletionSupport() const override { return false; } + + const GeneratorOptions* options_; +}; + +class GeneratorFactory : public clang::tooling::FrontendActionFactory { + public: + explicit GeneratorFactory(GeneratorOptions options = {}) + : options_(std::move(options)) {} + + private: + clang::FrontendAction* create() override { + return new GeneratorAction(&options_); + } + + GeneratorOptions options_; +}; + +} // namespace sapi + +#endif // SANDBOXED_API_TOOLS_CLANG_GENERATOR_GENERATOR_H_ \ No newline at end of file diff --git a/sandboxed_api/tools/clang_generator/generator_tool.cc b/sandboxed_api/tools/clang_generator/generator_tool.cc new file mode 100644 index 0000000..377e741 --- /dev/null +++ b/sandboxed_api/tools/clang_generator/generator_tool.cc @@ -0,0 +1,104 @@ +// 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 +#include + +#include "absl/memory/memory.h" +#include "clang/AST/ASTContext.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "llvm/Support/CommandLine.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/tools/clang_generator/generator.h" + +namespace sapi { +namespace { + +static auto* g_tool_category = + new llvm::cl::OptionCategory("Sandboxed API Options"); + +static auto* g_common_help = + new llvm::cl::extrahelp(clang::tooling::CommonOptionsParser::HelpMessage); +static auto* g_extra_help = new llvm::cl::extrahelp( + "Full documentation at: \n" + "Report bugs to \n"); + +// Command line options +static auto* g_sapi_embed_dir = new llvm::cl::opt( + "sapi_embed_dir", llvm::cl::desc("Directory with embedded includes"), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_embed_name = new llvm::cl::opt( + "sapi_embed_name", llvm::cl::desc("Identifier of the embed object"), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_functions = new llvm::cl::list( + "sapi_functions", llvm::cl::CommaSeparated, + llvm::cl::desc("List of functions to generate a Sandboxed API for. If " + "empty, generates a SAPI for all functions found."), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_in = new llvm::cl::list( + "sapi_in", llvm::cl::CommaSeparated, + llvm::cl::desc("List of input files to analyze. Deprecated, use positional " + "arguments instead."), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_isystem = new llvm::cl::opt( + "sapi_isystem", llvm::cl::desc("Extra system include paths"), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_limit_scan_depth = new llvm::cl::opt( + "sapi_limit_scan_depth", + llvm::cl::desc( + "Whether to only scan for functions in the top-most translation unit"), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_name = new llvm::cl::opt( + "sapi_name", llvm::cl::desc("Name of the Sandboxed API library"), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_ns = new llvm::cl::opt( + "sapi_ns", llvm::cl::desc("C++ namespace to wrap Sandboxed API class in"), + llvm::cl::cat(*g_tool_category)); +static auto* g_sapi_out = new llvm::cl::opt( + "sapi_out", + llvm::cl::desc( + "Ouput path of the generated header. If empty, simply appends .sapi.h " + "to the basename of the first source file specified."), + llvm::cl::cat(*g_tool_category)); + +} // namespace + +GeneratorOptions GeneratorOptionsFromFlags() { + GeneratorOptions options; + options.function_names.insert(g_sapi_functions->begin(), + g_sapi_functions->end()); + options.work_dir = sandbox2::file_util::fileops::GetCWD(); + options.name = *g_sapi_name; + options.namespace_name = *g_sapi_ns; + options.out_file = *g_sapi_out; + return options; +} + +} // namespace sapi + +int main(int argc, const char** argv) { + clang::tooling::CommonOptionsParser opt_parser( + argc, argv, *sapi::g_tool_category, llvm::cl::ZeroOrMore, + "Generates a Sandboxed API header for C/C++ translation units."); + std::vector sources = opt_parser.getSourcePathList(); + for (const auto& sapi_in : *sapi::g_sapi_in) { + sources.push_back(sapi_in); + } + + clang::tooling::ClangTool tool(opt_parser.getCompilations(), sources); + return tool.run(absl::make_unique( + sapi::GeneratorOptionsFromFlags()) + .get()); +} diff --git a/sandboxed_api/tools/clang_generator/types.cc b/sandboxed_api/tools/clang_generator/types.cc new file mode 100644 index 0000000..bc68cc1 --- /dev/null +++ b/sandboxed_api/tools/clang_generator/types.cc @@ -0,0 +1,178 @@ +// 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/types.h" + +#include "absl/strings/str_cat.h" + +namespace sapi { + +void GatherRelatedTypes(const clang::ASTContext& context, clang::QualType qual, + QualTypeSet* types) { + if (const auto* typedef_type = qual->getAs()) { + GatherRelatedTypes(context, typedef_type->getDecl()->getUnderlyingType(), + types); + types->insert(qual); + return; + } + + if (qual->isFunctionPointerType() || qual->isFunctionReferenceType() || + qual->isMemberFunctionPointerType()) { + if (const auto* function_type = qual->getPointeeOrArrayElementType() + ->getAs()) { + // Note: Do not add the function type itself, as this will always be a + // pointer argument. We only need to collect all its related types. + GatherRelatedTypes(context, function_type->getReturnType(), types); + for (const clang::QualType& param : function_type->getParamTypes()) { + GatherRelatedTypes(context, param, types); + } + return; + } + } + + if (IsPointerOrReference(qual)) { + clang::QualType pointee = qual->getPointeeType(); + while (IsPointerOrReference(pointee)) { + pointee = pointee->getPointeeType(); + } + GatherRelatedTypes(context, pointee, types); + return; + } + + // C array with specified constant size (i.e. int a[42])? + if (const clang::ArrayType* array_type = qual->getAsArrayTypeUnsafe()) { + GatherRelatedTypes(context, array_type->getElementType(), types); + return; + } + + if (IsSimple(qual) || qual->isEnumeralType()) { + if (const clang::EnumType* enum_type = qual->getAs()) { + // Collect the underlying integer type of enum classes as well, as it may + // be a typedef. + if (const clang::EnumDecl* decl = enum_type->getDecl(); decl->isFixed()) { + GatherRelatedTypes(context, decl->getIntegerType(), types); + } + } + types->insert(qual); + return; + } + + if (const auto* record_type = qual->getAs()) { + const clang::RecordDecl* decl = record_type->getDecl(); + for (const clang::FieldDecl* field : decl->fields()) { + GatherRelatedTypes(context, field->getType(), types); + } + types->insert(qual); + return; + } +} + +std::string MapQualType(clang::QualType qual) { + if (const auto* builtin = qual->getAs()) { + switch (builtin->getKind()) { + case clang::BuiltinType::Void: + case clang::BuiltinType::NullPtr: + return "::sapi::v::Void"; + + /* + * Unsigned types + */ + case clang::BuiltinType::Bool: + return "::sapi::v::Bool"; + // Unsigned character types + case clang::BuiltinType::Char_U: + case clang::BuiltinType::UChar: + return "::sapi::v::UChar"; + case clang::BuiltinType::WChar_U: + return "::sapi::v::ULong"; // 32-bit, correct for Linux and UTF-32 + // Added in C++20 + case clang::BuiltinType::Char8: // Underlying type: unsigned char + return "::sapi::v::UChar"; + case clang::BuiltinType::Char16: // Underlying type: uint_least16_t + return "::sapi::v::UShort"; + case clang::BuiltinType::Char32: // Underlying type: uint_least32_t + return "::sapi::v::ULong"; + // Standard unsigned types + case clang::BuiltinType::UShort: + return "::sapi::v::UShort"; + case clang::BuiltinType::UInt: + return "::sapi::v::UInt"; + case clang::BuiltinType::ULong: + return "::sapi::v::ULong"; + case clang::BuiltinType::ULongLong: + return "::sapi::v::ULLong"; + // TODO(cblichmann): Add 128-bit integer support + // case clang::BuiltinType::UInt128: + // return "::sapi::v::UInt128"; + + /* + * Signed types + */ + // Signed character types + case clang::BuiltinType::Char_S: + case clang::BuiltinType::SChar: + return "::sapi::v::Char"; + case clang::BuiltinType::WChar_S: + return "::sapi::v::Long"; // 32-bit, correct for Linux and UTF-32 + + // Standard signed types + case clang::BuiltinType::Short: + return "::sapi::v::Short"; + case clang::BuiltinType::Int: + return "::sapi::v::Int"; + case clang::BuiltinType::Long: + return "::sapi::v::Long"; + case clang::BuiltinType::LongLong: + return "::sapi::v::LLong"; + // TODO(cblichmann): Add 128-bit integer support + // case clang::BuiltinType::Int128: + // return "::sapi::v::Int128"; + + /* + * Floating-point types + */ + // TODO(cblichmann): Map half/__fp16, _Float16 and __float128 types + case clang::BuiltinType::Float: + return "::sapi::v::Reg"; + case clang::BuiltinType::Double: + return "::sapi::v::Reg"; + case clang::BuiltinType::LongDouble: + return "::sapi::v::Reg"; + + default: + break; + } + } else if (const auto* enum_type = qual->getAs()) { + return absl::StrCat("::sapi::v::IntBase<", + enum_type->getDecl()->getQualifiedNameAsString(), ">"); + } + // Best-effort mapping to "int" + return "::sapi::v::Int"; +} + +std::string MapQualTypeParameter(clang::QualType qual) { + // TODO(cblichmann): Define additional mappings, as appropriate + // _Bool -> bool + // unsigned long long -> uint64_t (where applicable) + // ... + 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), ">"); +} + +} // namespace sapi diff --git a/sandboxed_api/tools/clang_generator/types.h b/sandboxed_api/tools/clang_generator/types.h new file mode 100644 index 0000000..c5203bb --- /dev/null +++ b/sandboxed_api/tools/clang_generator/types.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef SANDBOXED_API_TOOLS_CLANG_GENERATOR_TYPES_H_ +#define SANDBOXED_API_TOOLS_CLANG_GENERATOR_TYPES_H_ + +#include +#include + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" + +namespace sapi { + +using QualTypeSet = + llvm::SetVector, + llvm::SmallPtrSet>; + +// Returns whether a type is "simple". Simple types are arithemtic types, +// i.e. signed and unsigned integer, character and bool types, as well as +// "void". +inline bool IsSimple(clang::QualType qual) { + return qual->isArithmeticType() || qual->isVoidType(); +} + +inline bool IsPointerOrReference(clang::QualType qual) { + return qual->isPointerType() || qual->isMemberPointerType() || + qual->isLValueReferenceType() || qual->isRValueReferenceType(); +} + +// Computes the transitive closure of all types that a type depends on. Those +// are types that need to be declared before a declaration of the type denoted +// by the qual parameter is valid. For example, given +// struct SubStruct { bool truth_value; }; +// struct AggregateStruct { +// int int_member; +// SubStruct struct_member; +// }; +// +// calling this function on the type "AggregateStruct" yields these types: +// int +// SubStruct +// bool +void GatherRelatedTypes(const clang::ASTContext& context, clang::QualType qual, + QualTypeSet* types); + +// 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); + +// 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); + +// Maps a qualified type used as a function return type to a type name +// 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); + +} // namespace sapi + +#endif // SANDBOXED_API_TOOLS_CLANG_GENERATOR_TYPES_H_