From 143e539d79ae4287738c02e6f5cc0ab1962d9b52 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Tue, 25 Jun 2019 14:48:56 +0200 Subject: [PATCH 1/3] First MVP of a LibTooling based SAPI header generator - Extract dependent types directly from the Clang AST and re-serialize back into compilable code - Collect types and emit diagnostics - Format generated code Signed-off-by: Christian Blichmann --- .gitignore | 5 + CMakeLists.txt | 16 ++ cmake/SapiBuildDefs.cmake | 47 ++-- cmake/SapiDeps.cmake | 10 +- cmake/SapiOptions.cmake | 3 + sandboxed_api/CMakeLists.txt | 5 +- .../sandbox2/examples/zlib/CMakeLists.txt | 2 + sandboxed_api/sandbox2/util/fileops.cc | 10 +- sandboxed_api/sandbox2/util/fileops.h | 4 + .../tools/clang_generator/CMakeLists.txt | 61 +++++ .../tools/clang_generator/diagnostics.cc | 56 +++++ .../tools/clang_generator/diagnostics.h | 40 ++++ .../tools/clang_generator/emitter.cc | 225 ++++++++++++++++++ sandboxed_api/tools/clang_generator/emitter.h | 101 ++++++++ .../tools/clang_generator/generator.cc | 132 ++++++++++ .../tools/clang_generator/generator.h | 123 ++++++++++ .../tools/clang_generator/generator_tool.cc | 104 ++++++++ sandboxed_api/tools/clang_generator/types.cc | 178 ++++++++++++++ sandboxed_api/tools/clang_generator/types.h | 77 ++++++ 19 files changed, 1177 insertions(+), 22 deletions(-) create mode 100644 sandboxed_api/tools/clang_generator/CMakeLists.txt create mode 100644 sandboxed_api/tools/clang_generator/diagnostics.cc create mode 100644 sandboxed_api/tools/clang_generator/diagnostics.h create mode 100644 sandboxed_api/tools/clang_generator/emitter.cc create mode 100644 sandboxed_api/tools/clang_generator/emitter.h create mode 100644 sandboxed_api/tools/clang_generator/generator.cc create mode 100644 sandboxed_api/tools/clang_generator/generator.h create mode 100644 sandboxed_api/tools/clang_generator/generator_tool.cc create mode 100644 sandboxed_api/tools/clang_generator/types.cc create mode 100644 sandboxed_api/tools/clang_generator/types.h 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_ From ae473c4bd1445036a91b322eb6119be02951a560 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 15 May 2020 15:36:09 +0200 Subject: [PATCH 2/3] Fix build issue if an incompatible local version of libunwind is installed. --- cmake/glog/Download.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/glog/Download.cmake b/cmake/glog/Download.cmake index aa9a291..7574072 100644 --- a/cmake/glog/Download.cmake +++ b/cmake/glog/Download.cmake @@ -36,6 +36,7 @@ endif() set(WITH_GFLAGS OFF CACHE BOOL "" FORCE) set(HAVE_LIB_GFLAGS 1) +set(WITH_UNWIND OFF CACHE BOOL "" FORCE) set(UNWIND_LIBRARY FALSE) set(HAVE_PWD_H FALSE) From 507eb00a90ce02c7f70c17da3cf8479a23a58f26 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 15 May 2020 15:44:44 +0200 Subject: [PATCH 3/3] 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