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 <mail@blichmann.eu>
This commit is contained in:
Christian Blichmann 2019-06-25 14:48:56 +02:00 committed by Christian Blichmann
parent dd4e81bccb
commit 143e539d79
19 changed files with 1177 additions and 22 deletions

5
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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})

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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<char, void (*)(char*)> cwd{getcwd(nullptr, 0),
[](char* p) { free(p); }};
std::unique_ptr<char, void (*)(char*)> 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,

View File

@ -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);

View File

@ -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
)

View File

@ -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<char*>(&raw_loc), sizeof(raw_loc)));
status.SetPayload(kSapiStatusPayload, std::move(payload));
return status;
}
absl::optional<clang::SourceLocation> 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<const uint64_t*>(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

View File

@ -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<clang::SourceLocation> 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_

View File

@ -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<absl::string_view::value_type>;
constexpr int kRandomIdLen = 8;
std::string random_id;
random_id.reserve(kRandomIdLen);
for (int i = 0; i < kRandomIdLen; ++i) {
random_id += static_cast<absl::string_view::value_type>(
absl::Uniform<Char>(*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<std::vector<std::string>> GetQualifiedNamePath(
const clang::TypeDecl* decl) {
std::vector<std::string> comps;
for (const auto* ctx = decl->getDeclContext(); ctx; ctx = ctx->getParent()) {
if (llvm::isa<clang::TranslationUnitDecl>(ctx)) {
continue;
}
const auto* nd = llvm::dyn_cast<clang::NamespaceDecl>(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<std::string> 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<OptionalReturn> FunctionName("
absl::StrAppend(&out, MapQualTypeReturn(return_type), " ", function_name,
"(");
struct ParameterInfo {
clang::QualType qual;
std::string name;
};
std::vector<ParameterInfo> params;
std::string print_separator;
for (int i = 0; i < decl->getNumParams(); ++i) {
const clang::ParmVarDecl* param = decl->getParamDecl(i);
ParameterInfo& 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<std::string> EmitHeader(
std::vector<clang::FunctionDecl*> 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<clang::TypedefType>()) {
decl = typedef_type->getDecl();
} else if (const auto* enum_type = qual->getAs<clang::EnumType>()) {
decl = enum_type->getDecl();
} else {
decl = qual->getAsRecordDecl();
}
if (decl) {
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

View File

@ -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 <string>
#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<std::string> EmitHeader(
std::vector<clang::FunctionDecl*> 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 <cstdint>
#include <type_traits>
#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_

View File

@ -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 <fstream>
#include <iostream>
#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<std::string> ReformatGoogleStyle(const std::string& filename,
const std::string& code) {
// Configure code style based on Google style, but enforce pointer alignment
clang::format::FormatStyle style =
clang::format::getGoogleStyle(clang::format::FormatStyle::LK_Cpp);
style.DerivePointerAlignment = false;
style.PointerAlignment = clang::format::FormatStyle::PAS_Left;
clang::tooling::Replacements replacements = clang::format::reformat(
style, code, llvm::makeArrayRef(clang::tooling::Range(0, code.size())),
filename);
llvm::Expected<std::string> formatted_header =
clang::tooling::applyAllReplacements(code, replacements);
if (!formatted_header) {
return absl::InternalError(llvm::toString(formatted_header.takeError()));
}
return *formatted_header;
}
} // namespace internal
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

View File

@ -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 <string>
#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 <typename ContainerT>
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<std::string> 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<GeneratorASTVisitor> {
public:
bool VisitFunctionDecl(clang::FunctionDecl* decl);
private:
friend class GeneratorASTConsumer;
const GeneratorOptions* options_ = nullptr;
std::vector<clang::FunctionDecl*> functions_;
QualTypeSet types_;
};
namespace internal {
sapi::StatusOr<std::string> 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<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance&, llvm::StringRef in_file) override {
return absl::make_unique<GeneratorASTConsumer>(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_

View File

@ -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 <string>
#include <vector>
#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: <https://developers.google.com/sandboxed-api/>\n"
"Report bugs to <https://github.com/google/sandboxed-api/issues>\n");
// Command line options
static auto* g_sapi_embed_dir = new llvm::cl::opt<std::string>(
"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<std::string>(
"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<std::string>(
"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<std::string>(
"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<std::string>(
"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<bool>(
"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<std::string>(
"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<std::string>(
"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<std::string>(
"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<std::string> 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::GeneratorFactory>(
sapi::GeneratorOptionsFromFlags())
.get());
}

View File

@ -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<clang::TypedefType>()) {
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<clang::FunctionProtoType>()) {
// 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<clang::EnumType>()) {
// 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<clang::RecordType>()) {
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<clang::BuiltinType>()) {
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<float>";
case clang::BuiltinType::Double:
return "::sapi::v::Reg<double>";
case clang::BuiltinType::LongDouble:
return "::sapi::v::Reg<long double>";
default:
break;
}
} else if (const auto* enum_type = qual->getAs<clang::EnumType>()) {
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

View File

@ -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 <string>
#include <vector>
#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<clang::QualType, std::vector<clang::QualType>,
llvm::SmallPtrSet<clang::QualType, 8>>;
// 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_