Repository: carbon-language/carbon-lang
File: toolchain/driver/clang_runner.cpp (C++)

Code before:
  // still subprocess. See `InProcess` comment at:
  // https://github.com/llvm/llvm-project/blob/86ce8e4504c06ecc3cc42f002ad4eb05cac10925/clang/lib/Driver/Job.cpp#L411-L413
  //
  // Note the subprocessing will effectively call `clang -cc1`, which turns into
  // `carbon-busybox clang -cc1`, which results in an equivalent `clang_main`
  // call.
  //
  // Also note that we only do `-disable-free` filtering in the in-process
  // execution here, as subprocesses leaking memory won't impact this process.
  auto cc1_main = [enable_leaking = enable_leaking_](
                      llvm::SmallVectorImpl<const char*>& cc1_args) -> int {
    // Clang doesn't expose any option to disable injecting `-disable-free` into
    // the CC1 invocation, or any way to append a flag that undoes it. So to
    // avoid leaks, we need to edit the `cc1_args` here and remove
    // `-disable-free`.
    //
    // TODO: We should see if upstream would be open to some configuration hook
    // to suppress this when it is generating the CC1 flags and use that.
    if (!enable_leaking) {
      llvm::StringRef flag = "-disable-free";
      if (const char** it = llvm::find(cc1_args, flag); it != cc1_args.end()) {
        cc1_args.erase(it);
      }
      // There should only ever be one of these flags, but let's be sure.
      CARBON_CHECK(llvm::find(cc1_args, flag) == cc1_args.end(),
                   "Found duplicate `-disable-free` flag!");
    }

    // cc1_args[0] will be the `clang_path` so we don't need the prepend arg.
    llvm::ToolContext tool_context = {
        .Path = cc1_args[0], .PrependArg = "clang", .NeedsPrependArg = false};
    return clang_main(cc1_args.size(), const_cast<char**>(cc1_args.data()),
                      tool_context);
  };
  driver.CC1Main = cc1_main;

  std::unique_ptr<clang::driver::Compilation> compilation(
      driver.BuildCompilation(cstr_args));
  CARBON_CHECK(compilation, "Should always successfully allocate!");
  if (compilation->containsError()) {
    // These should have been diagnosed by the driver.
    return false;
  }

  CARBON_VLOG("Running Clang driver...\n");

  llvm::SmallVector<std::pair<int, const clang::driver::Command*>>
      failing_commands;
  int result = driver.ExecuteCompilation(*compilation, failing_commands);

  // Finish diagnosing any failures before we verbosely log the source of those

Review comment (bug):
Would it make sense to test that the runner doesn't crash with something like `runner.Run({"-c", file.string(), "-o", output.string(), "-disable-free", "-disable-free"});` (sorry, probably not phrasing the flags correctly to get them forwarded)? If we're forwarding flags from the command line, we may need to be resilient to this kind of input.

That also makes me ask, had you considered the slightly different:

```suggestion
      // There should only be one flag instance, but this will handle multiple if present.
      llvm::erase(cc1_args, "-disable-free");
```

Code after:
  // still subprocess. See `InProcess` comment at:
  // https://github.com/llvm/llvm-project/blob/86ce8e4504c06ecc3cc42f002ad4eb05cac10925/clang/lib/Driver/Job.cpp#L411-L413
  //
  // Note the subprocessing will effectively call `clang -cc1`, which turns into
  // `carbon-busybox clang -cc1`, which results in an equivalent `clang_main`
  // call.
  //
  // Also note that we only do `-disable-free` filtering in the in-process
  // execution here, as subprocesses leaking memory won't impact this process.
  auto cc1_main = [enable_leaking = enable_leaking_](
                      llvm::SmallVectorImpl<const char*>& cc1_args) -> int {
    // Clang doesn't expose any option to disable injecting `-disable-free` into
    // the CC1 invocation, or any way to append a flag that undoes it. So to
    // avoid leaks, we need to edit the `cc1_args` here and remove
    // `-disable-free`.
    //
    // TODO: We should see if upstream would be open to some configuration hook
    // to suppress this when it is generating the CC1 flags and use that.
    if (!enable_leaking) {
      llvm::erase(cc1_args, llvm::StringRef("-disable-free"));
    }

    // cc1_args[0] will be the `clang_path` so we don't need the prepend arg.
    llvm::ToolContext tool_context = {
        .Path = cc1_args[0], .PrependArg = "clang", .NeedsPrependArg = false};
    return clang_main(cc1_args.size(), const_cast<char**>(cc1_args.data()),
                      tool_context);
  };
  driver.CC1Main = cc1_main;

  std::unique_ptr<clang::driver::Compilation> compilation(
      driver.BuildCompilation(cstr_args));
  CARBON_CHECK(compilation, "Should always successfully allocate!");
  if (compilation->containsError()) {
    // These should have been diagnosed by the driver.
    return false;
  }

  CARBON_VLOG("Running Clang driver...\n");

  llvm::SmallVector<std::pair<int, const clang::driver::Command*>>
      failing_commands;
  int result = driver.ExecuteCompilation(*compilation, failing_commands);

  // Finish diagnosing any failures before we verbosely log the source of those
  // failures.
  diagnostic_client.finish();

  CARBON_VLOG("Execution result code: {0}\n", result);
  for (const auto& [command_result, failing_command] : failing_commands) {
    CARBON_VLOG("Failing command '{0}' with code '{1}' was:\n",