Path: blob/main/contrib/llvm-project/clang/lib/Driver/Job.cpp
35233 views
//===- Job.cpp - Command to Execute ---------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Driver/Job.h"9#include "clang/Basic/LLVM.h"10#include "clang/Driver/Driver.h"11#include "clang/Driver/DriverDiagnostic.h"12#include "clang/Driver/InputInfo.h"13#include "clang/Driver/Tool.h"14#include "clang/Driver/ToolChain.h"15#include "llvm/ADT/ArrayRef.h"16#include "llvm/ADT/SmallString.h"17#include "llvm/ADT/SmallVector.h"18#include "llvm/ADT/StringExtras.h"19#include "llvm/ADT/StringRef.h"20#include "llvm/ADT/StringSet.h"21#include "llvm/ADT/StringSwitch.h"22#include "llvm/Support/CrashRecoveryContext.h"23#include "llvm/Support/FileSystem.h"24#include "llvm/Support/Path.h"25#include "llvm/Support/PrettyStackTrace.h"26#include "llvm/Support/Program.h"27#include "llvm/Support/raw_ostream.h"28#include <algorithm>29#include <cassert>30#include <cstddef>31#include <string>32#include <system_error>33#include <utility>3435using namespace clang;36using namespace driver;3738Command::Command(const Action &Source, const Tool &Creator,39ResponseFileSupport ResponseSupport, const char *Executable,40const llvm::opt::ArgStringList &Arguments,41ArrayRef<InputInfo> Inputs, ArrayRef<InputInfo> Outputs,42const char *PrependArg)43: Source(Source), Creator(Creator), ResponseSupport(ResponseSupport),44Executable(Executable), PrependArg(PrependArg), Arguments(Arguments) {45for (const auto &II : Inputs)46if (II.isFilename())47InputInfoList.push_back(II);48for (const auto &II : Outputs)49if (II.isFilename())50OutputFilenames.push_back(II.getFilename());51}5253/// Check if the compiler flag in question should be skipped when54/// emitting a reproducer. Also track how many arguments it has and if the55/// option is some kind of include path.56static bool skipArgs(const char *Flag, bool HaveCrashVFS, int &SkipNum,57bool &IsInclude) {58SkipNum = 2;59// These flags are all of the form -Flag <Arg> and are treated as two60// arguments. Therefore, we need to skip the flag and the next argument.61bool ShouldSkip = llvm::StringSwitch<bool>(Flag)62.Cases("-MF", "-MT", "-MQ", "-serialize-diagnostic-file", true)63.Cases("-o", "-dependency-file", true)64.Cases("-fdebug-compilation-dir", "-diagnostic-log-file", true)65.Cases("-dwarf-debug-flags", "-ivfsoverlay", true)66.Default(false);67if (ShouldSkip)68return true;6970// Some include flags shouldn't be skipped if we have a crash VFS71IsInclude = llvm::StringSwitch<bool>(Flag)72.Cases("-include", "-header-include-file", true)73.Cases("-idirafter", "-internal-isystem", "-iwithprefix", true)74.Cases("-internal-externc-isystem", "-iprefix", true)75.Cases("-iwithprefixbefore", "-isystem", "-iquote", true)76.Cases("-isysroot", "-I", "-F", "-resource-dir", true)77.Cases("-iframework", "-include-pch", true)78.Default(false);79if (IsInclude)80return !HaveCrashVFS;8182// The remaining flags are treated as a single argument.8384// These flags are all of the form -Flag and have no second argument.85ShouldSkip = llvm::StringSwitch<bool>(Flag)86.Cases("-M", "-MM", "-MG", "-MP", "-MD", true)87.Case("-MMD", true)88.Default(false);8990// Match found.91SkipNum = 1;92if (ShouldSkip)93return true;9495// These flags are treated as a single argument (e.g., -F<Dir>).96StringRef FlagRef(Flag);97IsInclude = FlagRef.starts_with("-F") || FlagRef.starts_with("-I");98if (IsInclude)99return !HaveCrashVFS;100if (FlagRef.starts_with("-fmodules-cache-path="))101return true;102103SkipNum = 0;104return false;105}106107void Command::writeResponseFile(raw_ostream &OS) const {108// In a file list, we only write the set of inputs to the response file109if (ResponseSupport.ResponseKind == ResponseFileSupport::RF_FileList) {110for (const auto *Arg : InputFileList) {111OS << Arg << '\n';112}113return;114}115116// In regular response files, we send all arguments to the response file.117// Wrapping all arguments in double quotes ensures that both Unix tools and118// Windows tools understand the response file.119for (const auto *Arg : Arguments) {120OS << '"';121122for (; *Arg != '\0'; Arg++) {123if (*Arg == '\"' || *Arg == '\\') {124OS << '\\';125}126OS << *Arg;127}128129OS << "\" ";130}131}132133void Command::buildArgvForResponseFile(134llvm::SmallVectorImpl<const char *> &Out) const {135// When not a file list, all arguments are sent to the response file.136// This leaves us to set the argv to a single parameter, requesting the tool137// to read the response file.138if (ResponseSupport.ResponseKind != ResponseFileSupport::RF_FileList) {139Out.push_back(Executable);140Out.push_back(ResponseFileFlag.c_str());141return;142}143144llvm::StringSet<> Inputs;145for (const auto *InputName : InputFileList)146Inputs.insert(InputName);147Out.push_back(Executable);148149if (PrependArg)150Out.push_back(PrependArg);151152// In a file list, build args vector ignoring parameters that will go in the153// response file (elements of the InputFileList vector)154bool FirstInput = true;155for (const auto *Arg : Arguments) {156if (Inputs.count(Arg) == 0) {157Out.push_back(Arg);158} else if (FirstInput) {159FirstInput = false;160Out.push_back(ResponseSupport.ResponseFlag);161Out.push_back(ResponseFile);162}163}164}165166/// Rewrite relative include-like flag paths to absolute ones.167static void168rewriteIncludes(const llvm::ArrayRef<const char *> &Args, size_t Idx,169size_t NumArgs,170llvm::SmallVectorImpl<llvm::SmallString<128>> &IncFlags) {171using namespace llvm;172using namespace sys;173174auto getAbsPath = [](StringRef InInc, SmallVectorImpl<char> &OutInc) -> bool {175if (path::is_absolute(InInc)) // Nothing to do here...176return false;177std::error_code EC = fs::current_path(OutInc);178if (EC)179return false;180path::append(OutInc, InInc);181return true;182};183184SmallString<128> NewInc;185if (NumArgs == 1) {186StringRef FlagRef(Args[Idx + NumArgs - 1]);187assert((FlagRef.starts_with("-F") || FlagRef.starts_with("-I")) &&188"Expecting -I or -F");189StringRef Inc = FlagRef.slice(2, StringRef::npos);190if (getAbsPath(Inc, NewInc)) {191SmallString<128> NewArg(FlagRef.slice(0, 2));192NewArg += NewInc;193IncFlags.push_back(std::move(NewArg));194}195return;196}197198assert(NumArgs == 2 && "Not expecting more than two arguments");199StringRef Inc(Args[Idx + NumArgs - 1]);200if (!getAbsPath(Inc, NewInc))201return;202IncFlags.push_back(SmallString<128>(Args[Idx]));203IncFlags.push_back(std::move(NewInc));204}205206void Command::Print(raw_ostream &OS, const char *Terminator, bool Quote,207CrashReportInfo *CrashInfo) const {208// Always quote the exe.209OS << ' ';210llvm::sys::printArg(OS, Executable, /*Quote=*/true);211212ArrayRef<const char *> Args = Arguments;213SmallVector<const char *, 128> ArgsRespFile;214if (ResponseFile != nullptr) {215buildArgvForResponseFile(ArgsRespFile);216Args = ArrayRef<const char *>(ArgsRespFile).slice(1); // no executable name217} else if (PrependArg) {218OS << ' ';219llvm::sys::printArg(OS, PrependArg, /*Quote=*/true);220}221222bool HaveCrashVFS = CrashInfo && !CrashInfo->VFSPath.empty();223for (size_t i = 0, e = Args.size(); i < e; ++i) {224const char *const Arg = Args[i];225226if (CrashInfo) {227int NumArgs = 0;228bool IsInclude = false;229if (skipArgs(Arg, HaveCrashVFS, NumArgs, IsInclude)) {230i += NumArgs - 1;231continue;232}233234// Relative includes need to be expanded to absolute paths.235if (HaveCrashVFS && IsInclude) {236SmallVector<SmallString<128>, 2> NewIncFlags;237rewriteIncludes(Args, i, NumArgs, NewIncFlags);238if (!NewIncFlags.empty()) {239for (auto &F : NewIncFlags) {240OS << ' ';241llvm::sys::printArg(OS, F.c_str(), Quote);242}243i += NumArgs - 1;244continue;245}246}247248auto Found = llvm::find_if(InputInfoList, [&Arg](const InputInfo &II) {249return II.getFilename() == Arg;250});251if (Found != InputInfoList.end() &&252(i == 0 || StringRef(Args[i - 1]) != "-main-file-name")) {253// Replace the input file name with the crashinfo's file name.254OS << ' ';255StringRef ShortName = llvm::sys::path::filename(CrashInfo->Filename);256llvm::sys::printArg(OS, ShortName.str(), Quote);257continue;258}259}260261OS << ' ';262llvm::sys::printArg(OS, Arg, Quote);263}264265if (CrashInfo && HaveCrashVFS) {266OS << ' ';267llvm::sys::printArg(OS, "-ivfsoverlay", Quote);268OS << ' ';269llvm::sys::printArg(OS, CrashInfo->VFSPath.str(), Quote);270271// The leftover modules from the crash are stored in272// <name>.cache/vfs/modules273// Leave it untouched for pcm inspection and provide a clean/empty dir274// path to contain the future generated module cache:275// <name>.cache/vfs/repro-modules276SmallString<128> RelModCacheDir = llvm::sys::path::parent_path(277llvm::sys::path::parent_path(CrashInfo->VFSPath));278llvm::sys::path::append(RelModCacheDir, "repro-modules");279280std::string ModCachePath = "-fmodules-cache-path=";281ModCachePath.append(RelModCacheDir.c_str());282283OS << ' ';284llvm::sys::printArg(OS, ModCachePath, Quote);285}286287if (ResponseFile != nullptr) {288OS << "\n Arguments passed via response file:\n";289writeResponseFile(OS);290// Avoiding duplicated newline terminator, since FileLists are291// newline-separated.292if (ResponseSupport.ResponseKind != ResponseFileSupport::RF_FileList)293OS << "\n";294OS << " (end of response file)";295}296297OS << Terminator;298}299300void Command::setResponseFile(const char *FileName) {301ResponseFile = FileName;302ResponseFileFlag = ResponseSupport.ResponseFlag;303ResponseFileFlag += FileName;304}305306void Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) {307Environment.reserve(NewEnvironment.size() + 1);308Environment.assign(NewEnvironment.begin(), NewEnvironment.end());309Environment.push_back(nullptr);310}311312void Command::setRedirectFiles(313const std::vector<std::optional<std::string>> &Redirects) {314RedirectFiles = Redirects;315}316317void Command::PrintFileNames() const {318if (PrintInputFilenames) {319for (const auto &Arg : InputInfoList)320llvm::outs() << llvm::sys::path::filename(Arg.getFilename()) << "\n";321llvm::outs().flush();322}323}324325int Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,326std::string *ErrMsg, bool *ExecutionFailed) const {327PrintFileNames();328329SmallVector<const char *, 128> Argv;330if (ResponseFile == nullptr) {331Argv.push_back(Executable);332if (PrependArg)333Argv.push_back(PrependArg);334Argv.append(Arguments.begin(), Arguments.end());335Argv.push_back(nullptr);336} else {337// If the command is too large, we need to put arguments in a response file.338std::string RespContents;339llvm::raw_string_ostream SS(RespContents);340341// Write file contents and build the Argv vector342writeResponseFile(SS);343buildArgvForResponseFile(Argv);344Argv.push_back(nullptr);345SS.flush();346347// Save the response file in the appropriate encoding348if (std::error_code EC = writeFileWithEncoding(349ResponseFile, RespContents, ResponseSupport.ResponseEncoding)) {350if (ErrMsg)351*ErrMsg = EC.message();352if (ExecutionFailed)353*ExecutionFailed = true;354// Return -1 by convention (see llvm/include/llvm/Support/Program.h) to355// indicate the requested executable cannot be started.356return -1;357}358}359360std::optional<ArrayRef<StringRef>> Env;361std::vector<StringRef> ArgvVectorStorage;362if (!Environment.empty()) {363assert(Environment.back() == nullptr &&364"Environment vector should be null-terminated by now");365ArgvVectorStorage = llvm::toStringRefArray(Environment.data());366Env = ArrayRef(ArgvVectorStorage);367}368369auto Args = llvm::toStringRefArray(Argv.data());370371// Use Job-specific redirect files if they are present.372if (!RedirectFiles.empty()) {373std::vector<std::optional<StringRef>> RedirectFilesOptional;374for (const auto &Ele : RedirectFiles)375if (Ele)376RedirectFilesOptional.push_back(std::optional<StringRef>(*Ele));377else378RedirectFilesOptional.push_back(std::nullopt);379380return llvm::sys::ExecuteAndWait(Executable, Args, Env,381ArrayRef(RedirectFilesOptional),382/*secondsToWait=*/0, /*memoryLimit=*/0,383ErrMsg, ExecutionFailed, &ProcStat);384}385386return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects,387/*secondsToWait*/ 0, /*memoryLimit*/ 0,388ErrMsg, ExecutionFailed, &ProcStat);389}390391CC1Command::CC1Command(const Action &Source, const Tool &Creator,392ResponseFileSupport ResponseSupport,393const char *Executable,394const llvm::opt::ArgStringList &Arguments,395ArrayRef<InputInfo> Inputs, ArrayRef<InputInfo> Outputs,396const char *PrependArg)397: Command(Source, Creator, ResponseSupport, Executable, Arguments, Inputs,398Outputs, PrependArg) {399InProcess = true;400}401402void CC1Command::Print(raw_ostream &OS, const char *Terminator, bool Quote,403CrashReportInfo *CrashInfo) const {404if (InProcess)405OS << " (in-process)\n";406Command::Print(OS, Terminator, Quote, CrashInfo);407}408409int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,410std::string *ErrMsg, bool *ExecutionFailed) const {411// FIXME: Currently, if there're more than one job, we disable412// -fintegrate-cc1. If we're no longer a integrated-cc1 job, fallback to413// out-of-process execution. See discussion in https://reviews.llvm.org/D74447414if (!InProcess)415return Command::Execute(Redirects, ErrMsg, ExecutionFailed);416417PrintFileNames();418419SmallVector<const char *, 128> Argv;420Argv.push_back(getExecutable());421Argv.append(getArguments().begin(), getArguments().end());422Argv.push_back(nullptr);423Argv.pop_back(); // The terminating null element shall not be part of the424// slice (main() behavior).425426// This flag simply indicates that the program couldn't start, which isn't427// applicable here.428if (ExecutionFailed)429*ExecutionFailed = false;430431llvm::CrashRecoveryContext CRC;432CRC.DumpStackAndCleanupOnFailure = true;433434const void *PrettyState = llvm::SavePrettyStackState();435const Driver &D = getCreator().getToolChain().getDriver();436437int R = 0;438// Enter ExecuteCC1Tool() instead of starting up a new process439if (!CRC.RunSafely([&]() { R = D.CC1Main(Argv); })) {440llvm::RestorePrettyStackState(PrettyState);441return CRC.RetCode;442}443return R;444}445446void CC1Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) {447// We don't support set a new environment when calling into ExecuteCC1Tool()448llvm_unreachable(449"The CC1Command doesn't support changing the environment vars!");450}451452void JobList::Print(raw_ostream &OS, const char *Terminator, bool Quote,453CrashReportInfo *CrashInfo) const {454for (const auto &Job : *this)455Job.Print(OS, Terminator, Quote, CrashInfo);456}457458void JobList::clear() { Jobs.clear(); }459460461