From ec46efd739f15ee49f99834ba56c019cbf69e4ed Mon Sep 17 00:00:00 2001 From: Giuliano Belinassi Date: Fri, 7 Jun 2024 19:50:53 -0300 Subject: [PATCH] Allow clang-extract to ignore errors pointed by clang Sometime it is useful to not halt compilation if an error occurred because the generated code may still be useful when creating livepatches. Hence add a new option: ``` -DCE_IGNORE_CLANG_ERRORS ``` To ignore clang errors and continue compilation even so. Signed-off-by: Giuliano Belinassi --- README.md | 1 + libcextract/ASTUnitHack.cpp | 50 +++++++++++++++++++++++++++++++++ libcextract/ArgvParser.cpp | 8 ++++++ libcextract/ArgvParser.hh | 6 ++++ libcextract/Passes.cpp | 48 ++++++++++++++++++++++++++----- libcextract/Passes.hh | 4 +++ libcextract/meson.build | 3 +- testsuite/small/ignore-errors.c | 9 ++++++ 8 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 libcextract/ASTUnitHack.cpp create mode 100644 testsuite/small/ignore-errors.c diff --git a/README.md b/README.md index e7f5c83..a741bb9 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ Clang-extract support many options which controls the output code: - `-DCE_SYMVERS_PATH=` Path to kernel Modules.symvers file. Only used when `-D__KERNEL__` is specified. - `-DCE_DSC_OUTPUT=` Libpulp .dsc file output, used for userspace livepatching. - `-DCE_LATE_EXTERNALIZE` Enable late externalization (declare externalized variables later than the original). May reduce code output when `-DCE_KEEP_INCLUDES` is enabled. +- `-DCE_IGNORE_CLANG_ERRORS` Ignore clang compilation errors in a hope that code is generated even if it won't compile. For more switches, see ``` diff --git a/libcextract/ASTUnitHack.cpp b/libcextract/ASTUnitHack.cpp new file mode 100644 index 0000000..7b4b902 --- /dev/null +++ b/libcextract/ASTUnitHack.cpp @@ -0,0 +1,50 @@ +//===- ASTUnitHack.cpp - Hacks for the ASTUnit class ------------------*- C++ *-===// +// +// This project is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// \file +/// Hacks for the ASTUnit class. +// +//===----------------------------------------------------------------------===// + +/* Author: Giuliano Belinassi */ + +#include +#include +#include + +using namespace clang; + +/** Filesystem that will be used in our custom ASTUnit::create, so that way we + * don't break clang's API. + */ +IntrusiveRefCntPtr _Hack_VFS; + +/** Hack to create an ASTUnit from LoadFromCompilerInvocationAction with a + * virtual filesystem. That way we can use FrontendActions hooks when + * creating the ASTUnit. + */ +std::unique_ptr +ASTUnit::create(std::shared_ptr CI, + IntrusiveRefCntPtr Diags, + CaptureDiagsKind CaptureDiagnostics, + bool UserFilesAreVolatile) { + + std::unique_ptr AST(new ASTUnit(false)); + ConfigureDiags(Diags, *AST, CaptureDiagnostics); + + AST->Diagnostics = Diags; + AST->FileSystemOpts = CI->getFileSystemOpts(); + AST->Invocation = std::move(CI); + AST->FileMgr = new FileManager(AST->FileSystemOpts, _Hack_VFS); + AST->UserFilesAreVolatile = UserFilesAreVolatile; + AST->SourceMgr = new SourceManager(AST->getDiagnostics(), *AST->FileMgr, + UserFilesAreVolatile); + AST->ModuleCache = new InMemoryModuleCache; + + return AST; +} diff --git a/libcextract/ArgvParser.cpp b/libcextract/ArgvParser.cpp index 1686a6b..eaa3d1b 100644 --- a/libcextract/ArgvParser.cpp +++ b/libcextract/ArgvParser.cpp @@ -66,6 +66,7 @@ ArgvParser::ArgvParser(int argc, char **argv) SymbolsToExternalize(), HeadersToExpand(), OutputFile(), + IgnoreClangErrors(false), DisableExternalization(false), WithIncludes(false), DumpPasses(false), @@ -177,6 +178,8 @@ void ArgvParser::Print_Usage_Message(void) " -DCE_LATE_EXTERNALIZE Enable late externalization (declare externalized variables\n" " later than the original). May reduce code output when\n" " -DCE_KEEP_INCLUDES is enabled\n" +" -DCE_IGNORE_CLANG_ERRORS Ignore clang compilation errors in a hope that code is\n" +" generated even if it won't compile.\n" "\n"; llvm::outs() << "The following arguments are ignored by clang-extract:\n"; @@ -291,6 +294,11 @@ bool ArgvParser::Handle_Clang_Extract_Arg(const char *str) return true; } + if (!strcmp("-DCE_IGNORE_CLANG_ERRORS", str)) { + IgnoreClangErrors = true; + + return true; + } if (!strcmp("--help", str)) { Print_Usage_Message(); diff --git a/libcextract/ArgvParser.hh b/libcextract/ArgvParser.hh index c0a85eb..c298391 100644 --- a/libcextract/ArgvParser.hh +++ b/libcextract/ArgvParser.hh @@ -134,6 +134,11 @@ class ArgvParser return AllowLateExternalization; } + inline bool Get_Ignore_Clang_Errors(void) + { + return IgnoreClangErrors; + } + const char *Get_Input_File(void); /** Print help usage message. */ @@ -150,6 +155,7 @@ class ArgvParser std::vector HeadersToExpand; std::string OutputFile; + bool IgnoreClangErrors; bool DisableExternalization; bool WithIncludes; bool DumpPasses; diff --git a/libcextract/Passes.cpp b/libcextract/Passes.cpp index 6dc4ddb..fe9475d 100644 --- a/libcextract/Passes.cpp +++ b/libcextract/Passes.cpp @@ -45,6 +45,11 @@ void Print_AST(ASTUnit *ast) } } +/** Filesystem that will be used in our custom ASTUnit::create, so that way we + * don't break clang's API. See ASTUnitHack.cpp. + */ +extern IntrusiveRefCntPtr _Hack_VFS; + static bool Build_ASTUnit(PassManager::Context *ctx, IntrusiveRefCntPtr fs = nullptr) { ctx->AST.reset(); @@ -65,6 +70,8 @@ static bool Build_ASTUnit(PassManager::Context *ctx, IntrusiveRefCntPtrOFS; } + _Hack_VFS = fs; + /* Built the ASTUnit from the passed command line and set its SourceManager to the PrettyPrint class. */ DiagnosticOptions *diagopts = new DiagnosticOptions(); @@ -73,17 +80,44 @@ static bool Build_ASTUnit(PassManager::Context *ctx, IntrusiveRefCntPtrIgnoreClangErrors) { + Diags->setWarningsAsErrors(false); + Diags->setErrorsAsFatal(false); + Diags->setIgnoreAllWarnings(true); + } + CInvok = ClangCompat::createInvocationFromCommandLine(ctx->ClangArgs, Diags); - FileManager *FileMgr = new FileManager(FileSystemOptions(), fs); PCHContainerOps = std::make_shared(); - auto AU = ASTUnit::LoadFromCompilerInvocation( - CInvok, PCHContainerOps, Diags, FileMgr, false, CaptureDiagsKind::None, 0, - TU_Complete, false, false, false); + + /* Hacked function call, see ASTUnitHack.cpp. */ + auto AU = ASTUnit::create(CInvok, Diags, CaptureDiagsKind::None, false); + std::unique_ptr *ErrAST = nullptr; + + ASTUnit::LoadFromCompilerInvocationAction(CInvok, PCHContainerOps, + Diags, nullptr, AU.get(), + /*Persistent=*/true, + /*ResourceFilesPath=*/StringRef(), + /*OnlyLocalDecls=*/false, + /*CaptureDiagnostics=*/CaptureDiagsKind::None, + /*PrecompilePreambleAfterNParses=*/0, + /*CacheCodeCompletionResults=*/false, + /*UserFilesAreVolatile=*/false, + ErrAST); + + _Hack_VFS = nullptr; if (AU == nullptr) { - DiagsClass::Emit_Error("Unable to create ASTUnit object."); + if (ctx->IgnoreClangErrors && ErrAST) { + PrettyPrint::Set_AST(ErrAST->get()); + ctx->AST = std::move(*ErrAST); + + return true; + } + + throw std::runtime_error("Unable to create ASTUnit object."); return false; } @@ -336,7 +370,7 @@ class ClosurePass : public Pass /* If there was an error on building the AST here, don't continue. */ const DiagnosticsEngine &de = ctx->AST->getDiagnostics(); - if (de.hasErrorOccurred()) { + if (ctx->IgnoreClangErrors == false && de.hasErrorOccurred()) { return false; } @@ -689,7 +723,7 @@ int PassManager::Run_Passes(ArgvParser &args) pass->Dump_Result(&ctx); } - if (pass_success == false) { + if (ctx.IgnoreClangErrors == false && pass_success == false) { std::cerr << '\n' << "Error on pass: " << pass->PassName << '\n'; return -1; } diff --git a/libcextract/Passes.hh b/libcextract/Passes.hh index 0e52fbc..2ae9266 100644 --- a/libcextract/Passes.hh +++ b/libcextract/Passes.hh @@ -48,6 +48,7 @@ class PassManager { : FuncExtractNames(args.Get_Functions_To_Extract()), Externalize(args.Get_Symbols_To_Externalize()), OutputFile(args.Get_Output_File()), + IgnoreClangErrors(args.Get_Ignore_Clang_Errors()), ExternalizationDisabled(args.Is_Externalization_Disabled()), KeepIncludes(args.Should_Keep_Includes()), DumpPasses(args.Should_Dump_Passes()), @@ -90,6 +91,9 @@ class PassManager { /** The final output file name. */ std::string &OutputFile; + /** Should we ignore compilation errors from clang? */ + bool IgnoreClangErrors; + /** Is the externalization passes disabled? */ bool ExternalizationDisabled; diff --git a/libcextract/meson.build b/libcextract/meson.build index 5d8760f..c321add 100644 --- a/libcextract/meson.build +++ b/libcextract/meson.build @@ -33,7 +33,8 @@ libcextract_sources = [ 'TopLevelASTIterator.cpp', 'ExpansionPolicy.cpp', 'HeaderGenerate.cpp', - 'Closure.cpp' + 'Closure.cpp', + 'ASTUnitHack.cpp' ] libcextract_static = static_library('cextract', libcextract_sources) diff --git a/testsuite/small/ignore-errors.c b/testsuite/small/ignore-errors.c new file mode 100644 index 0000000..040c968 --- /dev/null +++ b/testsuite/small/ignore-errors.c @@ -0,0 +1,9 @@ +/* { dg-options "-DCE_EXTRACT_FUNCTIONS=f -DCE_NO_EXTERNALIZATION -DCE_IGNORE_CLANG_ERRORS" }*/ + +void f(void) +{ + return 3; +} + +/* { dg-final { scan-tree-dump "void f" } } */ +/* { dg-final { scan-tree-dump "return 3;" } } */