Skip to content

Commit 4c53c34

Browse files
committed
Swift: make TargetFile::good() a class invariant
Fallible initialization has been moved to a factory function, and `commit` has been moved to the destructor.
1 parent f7dca4d commit 4c53c34

File tree

3 files changed

+69
-41
lines changed

3 files changed

+69
-41
lines changed

swift/extractor/SwiftExtractor.cpp

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ static llvm::SmallVector<swift::Decl*> getTopLevelDecls(swift::ModuleDecl& modul
7777
return ret;
7878
}
7979

80+
static void dumpArgs(TargetFile& out, const SwiftExtractorConfiguration& config) {
81+
out << "/* extractor-args:\n";
82+
for (const auto& opt : config.frontendOptions) {
83+
out << " " << std::quoted(opt) << " \\\n";
84+
}
85+
out << "\n*/\n";
86+
87+
out << "/* swift-frontend-args:\n";
88+
for (const auto& opt : config.patchedFrontendOptions) {
89+
out << " " << std::quoted(opt) << " \\\n";
90+
}
91+
out << "\n*/\n";
92+
}
93+
8094
static void extractDeclarations(const SwiftExtractorConfiguration& config,
8195
swift::CompilerInstance& compiler,
8296
swift::ModuleDecl& module,
@@ -86,25 +100,13 @@ static void extractDeclarations(const SwiftExtractorConfiguration& config,
86100
// The extractor can be called several times from different processes with
87101
// the same input file(s). Using `TargetFile` the first process will win, and the following
88102
// will just skip the work
89-
TargetFile trapStream{filename + ".trap", config.trapDir, config.getTempTrapDir()};
90-
if (!trapStream.good()) {
103+
auto trapTarget = TargetFile::create(filename + ".trap", config.trapDir, config.getTempTrapDir());
104+
if (!trapTarget) {
91105
// another process arrived first, nothing to do for us
92106
return;
93107
}
94-
95-
trapStream << "/* extractor-args:\n";
96-
for (const auto& opt : config.frontendOptions) {
97-
trapStream << " " << std::quoted(opt) << " \\\n";
98-
}
99-
trapStream << "\n*/\n";
100-
101-
trapStream << "/* swift-frontend-args:\n";
102-
for (const auto& opt : config.patchedFrontendOptions) {
103-
trapStream << " " << std::quoted(opt) << " \\\n";
104-
}
105-
trapStream << "\n*/\n";
106-
107-
TrapDomain trap{trapStream};
108+
dumpArgs(*trapTarget, config);
109+
TrapDomain trap{*trapTarget};
108110

109111
// TODO: move default location emission elsewhere, possibly in a separate global trap file
110112
// the following cannot conflict with actual files as those have an absolute path starting with /
@@ -129,7 +131,6 @@ static void extractDeclarations(const SwiftExtractorConfiguration& config,
129131
auto fileLabel = trap.createLabel<FileTag>(name.str().str());
130132
trap.emit(FilesTrap{fileLabel, name.str().str()});
131133
}
132-
trapStream.commit();
133134
}
134135

135136
static std::unordered_set<std::string> collectInputFilenames(swift::CompilerInstance& compiler) {

swift/extractor/infra/TargetFile.cpp

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#include "swift/extractor/infra/TargetFile.h"
22

3+
#include <iostream>
4+
#include <cstdio>
5+
#include <cerrno>
6+
#include <system_error>
7+
38
#include <llvm/Support/FileSystem.h>
49
#include <llvm/Support/Path.h>
510

@@ -36,31 +41,49 @@ std::string initPath(std::string_view target, std::string_view dir) {
3641
TargetFile::TargetFile(std::string_view target,
3742
std::string_view targetDir,
3843
std::string_view workingDir)
39-
: workingPath{initPath(target, workingDir)}, targetPath{initPath(target, targetDir)} {
44+
: workingPath{initPath(target, workingDir)}, targetPath{initPath(target, targetDir)} {}
45+
46+
bool TargetFile::init() {
4047
errno = 0;
4148
// since C++17 "x" mode opens with O_EXCL (fails if file already exists)
4249
if (auto f = std::fopen(targetPath.c_str(), "wx")) {
4350
std::fclose(f);
4451
out.open(workingPath);
4552
checkOutput("open file for writing");
46-
} else {
47-
if (errno != EEXIST) {
48-
error("open file for writing", targetPath);
49-
}
50-
// else we just lost the race, do nothing (good() will return false to signal this)
53+
return true;
5154
}
55+
if (errno != EEXIST) {
56+
error("open file for writing", targetPath);
57+
}
58+
// else we just lost the race
59+
return false;
60+
}
61+
62+
std::optional<TargetFile> TargetFile::create(std::string_view target,
63+
std::string_view targetDir,
64+
std::string_view workingDir) {
65+
TargetFile ret{target, targetDir, workingDir};
66+
if (ret.init()) return {std::move(ret)};
67+
return std::nullopt;
5268
}
5369

54-
bool TargetFile::good() const {
55-
return out && out.is_open();
70+
TargetFile& TargetFile::operator=(TargetFile&& other) {
71+
if (this != &other) {
72+
commit();
73+
workingPath = std::move(other.workingPath);
74+
targetPath = std::move(other.targetPath);
75+
out = std::move(other.out);
76+
}
77+
return *this;
5678
}
5779

5880
void TargetFile::commit() {
59-
assert(good());
60-
out.close();
61-
errno = 0;
62-
if (std::rename(workingPath.c_str(), targetPath.c_str()) != 0) {
63-
error("rename file", targetPath);
81+
if (out.is_open()) {
82+
out.close();
83+
errno = 0;
84+
if (std::rename(workingPath.c_str(), targetPath.c_str()) != 0) {
85+
error("rename file", targetPath);
86+
}
6487
}
6588
}
6689

@@ -69,5 +92,4 @@ void TargetFile::checkOutput(const char* action) {
6992
error(action, workingPath);
7093
}
7194
}
72-
7395
} // namespace codeql

swift/extractor/infra/TargetFile.h

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,29 @@
22

33
#include <string>
44
#include <fstream>
5-
#include <iostream>
6-
#include <cstdio>
5+
#include <optional>
76
#include <cerrno>
8-
#include <system_error>
9-
#include <sstream>
107

118
namespace codeql {
129

13-
// Only the first process trying to open an `TargetFile` for a given `target` is allowed to do
14-
// so, all others will have an instance with `good() == false` and failing on any other operation.
10+
// Only the first process trying to create a `TargetFile` for a given `target` is allowed to do
11+
// so, all others will have `create` return `std::nullopt`.
1512
// The content streamed to the `TargetFile` is written to `workingDir/target`, and is moved onto
16-
// `targetDir/target` only when `commit()` is called
13+
// `targetDir/target` on destruction.
1714
class TargetFile {
1815
std::string workingPath;
1916
std::string targetPath;
2017
std::ofstream out;
2118

2219
public:
23-
TargetFile(std::string_view target, std::string_view targetDir, std::string_view workingDir);
20+
static std::optional<TargetFile> create(std::string_view target,
21+
std::string_view targetDir,
22+
std::string_view workingDir);
2423

25-
bool good() const;
26-
void commit();
24+
~TargetFile() { commit(); }
25+
26+
TargetFile(TargetFile&& other) = default;
27+
TargetFile& operator=(TargetFile&& other);
2728

2829
template <typename T>
2930
TargetFile& operator<<(T&& value) {
@@ -34,7 +35,11 @@ class TargetFile {
3435
}
3536

3637
private:
38+
TargetFile(std::string_view target, std::string_view targetDir, std::string_view workingDir);
39+
40+
bool init();
3741
void checkOutput(const char* action);
42+
void commit();
3843
};
3944

4045
} // namespace codeql

0 commit comments

Comments
 (0)