Skip to content

Commit 7fbe4f8

Browse files
authored
Merge pull request #9815 from github/redsun82/swift-exclusive-file
Swift: trap output rework
2 parents 21066d2 + 22ff8c2 commit 7fbe4f8

File tree

16 files changed

+270
-171
lines changed

16 files changed

+270
-171
lines changed

swift/codegen/schema.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ AppliedPropertyWrapperExpr:
323323
_extends: Expr
324324

325325
Argument:
326+
_extends: Locatable
326327
label: string
327328
_children:
328329
expr: Expr

swift/extractor/SwiftExtractor.cpp

Lines changed: 30 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
#include "SwiftExtractor.h"
22

3-
#include <filesystem>
4-
#include <fstream>
53
#include <iostream>
6-
#include <sstream>
74
#include <memory>
8-
#include <unistd.h>
95
#include <unordered_set>
106
#include <queue>
117

@@ -16,8 +12,9 @@
1612
#include <llvm/Support/Path.h>
1713

1814
#include "swift/extractor/trap/generated/TrapClasses.h"
19-
#include "swift/extractor/trap/TrapOutput.h"
15+
#include "swift/extractor/trap/TrapDomain.h"
2016
#include "swift/extractor/visitors/SwiftVisitor.h"
17+
#include "swift/extractor/infra/TargetFile.h"
2118

2219
using namespace codeql;
2320

@@ -52,7 +49,7 @@ static void archiveFile(const SwiftExtractorConfiguration& config, swift::Source
5249
}
5350
}
5451

55-
static std::string getTrapFilename(swift::ModuleDecl& module, swift::SourceFile* primaryFile) {
52+
static std::string getFilename(swift::ModuleDecl& module, swift::SourceFile* primaryFile) {
5653
if (primaryFile) {
5754
return primaryFile->getFilename().str();
5855
}
@@ -76,60 +73,45 @@ static llvm::SmallVector<swift::Decl*> getTopLevelDecls(swift::ModuleDecl& modul
7673
return ret;
7774
}
7875

76+
static void dumpArgs(TargetFile& out, const SwiftExtractorConfiguration& config) {
77+
out << "/* extractor-args:\n";
78+
for (const auto& opt : config.frontendOptions) {
79+
out << " " << std::quoted(opt) << " \\\n";
80+
}
81+
out << "\n*/\n";
82+
83+
out << "/* swift-frontend-args:\n";
84+
for (const auto& opt : config.patchedFrontendOptions) {
85+
out << " " << std::quoted(opt) << " \\\n";
86+
}
87+
out << "\n*/\n";
88+
}
89+
7990
static void extractDeclarations(const SwiftExtractorConfiguration& config,
8091
swift::CompilerInstance& compiler,
8192
swift::ModuleDecl& module,
8293
swift::SourceFile* primaryFile = nullptr) {
94+
auto filename = getFilename(module, primaryFile);
95+
8396
// The extractor can be called several times from different processes with
84-
// the same input file(s)
85-
// We are using PID to avoid concurrent access
86-
// TODO: find a more robust approach to avoid collisions?
87-
auto name = getTrapFilename(module, primaryFile);
88-
llvm::StringRef filename(name);
89-
std::string tempTrapName = filename.str() + '.' + std::to_string(getpid()) + ".trap";
90-
llvm::SmallString<PATH_MAX> tempTrapPath(config.getTempTrapDir());
91-
llvm::sys::path::append(tempTrapPath, tempTrapName);
92-
93-
llvm::StringRef tempTrapParent = llvm::sys::path::parent_path(tempTrapPath);
94-
if (std::error_code ec = llvm::sys::fs::create_directories(tempTrapParent)) {
95-
std::cerr << "Cannot create temp trap directory '" << tempTrapParent.str()
96-
<< "': " << ec.message() << "\n";
97+
// the same input file(s). Using `TargetFile` the first process will win, and the following
98+
// will just skip the work
99+
auto trapTarget = TargetFile::create(filename + ".trap", config.trapDir, config.getTempTrapDir());
100+
if (!trapTarget) {
101+
// another process arrived first, nothing to do for us
97102
return;
98103
}
99-
100-
std::ofstream trapStream(tempTrapPath.str().str());
101-
if (!trapStream) {
102-
std::error_code ec;
103-
ec.assign(errno, std::generic_category());
104-
std::cerr << "Cannot create temp trap file '" << tempTrapPath.str().str()
105-
<< "': " << ec.message() << "\n";
106-
return;
107-
}
108-
trapStream << "/* extractor-args:\n";
109-
for (auto opt : config.frontendOptions) {
110-
trapStream << " " << std::quoted(opt) << " \\\n";
111-
}
112-
trapStream << "\n*/\n";
113-
114-
trapStream << "/* swift-frontend-args:\n";
115-
for (auto opt : config.patchedFrontendOptions) {
116-
trapStream << " " << std::quoted(opt) << " \\\n";
117-
}
118-
trapStream << "\n*/\n";
119-
120-
TrapOutput trap{trapStream};
121-
TrapArena arena{};
104+
dumpArgs(*trapTarget, config);
105+
TrapDomain trap{*trapTarget};
122106

123107
// TODO: move default location emission elsewhere, possibly in a separate global trap file
124-
auto unknownFileLabel = arena.allocateLabel<FileTag>();
125108
// the following cannot conflict with actual files as those have an absolute path starting with /
126-
trap.assignKey(unknownFileLabel, "unknown");
109+
auto unknownFileLabel = trap.createLabel<FileTag>("unknown");
110+
auto unknownLocationLabel = trap.createLabel<LocationTag>("unknown");
127111
trap.emit(FilesTrap{unknownFileLabel});
128-
auto unknownLocationLabel = arena.allocateLabel<LocationTag>();
129-
trap.assignKey(unknownLocationLabel, "unknown");
130112
trap.emit(LocationsTrap{unknownLocationLabel, unknownFileLabel});
131113

132-
SwiftVisitor visitor(compiler.getSourceMgr(), arena, trap, module, primaryFile);
114+
SwiftVisitor visitor(compiler.getSourceMgr(), trap, module, primaryFile);
133115
auto topLevelDecls = getTopLevelDecls(module, primaryFile);
134116
for (auto decl : topLevelDecls) {
135117
visitor.extract(decl);
@@ -142,28 +124,9 @@ static void extractDeclarations(const SwiftExtractorConfiguration& config,
142124
// fact that the file was extracted
143125
llvm::SmallString<PATH_MAX> name(filename);
144126
llvm::sys::fs::make_absolute(name);
145-
auto fileLabel = arena.allocateLabel<FileTag>();
146-
trap.assignKey(fileLabel, name.str().str());
127+
auto fileLabel = trap.createLabel<FileTag>(name.str().str());
147128
trap.emit(FilesTrap{fileLabel, name.str().str()});
148129
}
149-
150-
// TODO: Pick a better name to avoid collisions
151-
std::string trapName = filename.str() + ".trap";
152-
llvm::SmallString<PATH_MAX> trapPath(config.trapDir);
153-
llvm::sys::path::append(trapPath, trapName);
154-
155-
llvm::StringRef trapParent = llvm::sys::path::parent_path(trapPath);
156-
if (std::error_code ec = llvm::sys::fs::create_directories(trapParent)) {
157-
std::cerr << "Cannot create trap directory '" << trapParent.str() << "': " << ec.message()
158-
<< "\n";
159-
return;
160-
}
161-
162-
// TODO: The last process wins. Should we do better than that?
163-
if (std::error_code ec = llvm::sys::fs::rename(tempTrapPath, trapPath)) {
164-
std::cerr << "Cannot rename temp trap file '" << tempTrapPath.str().str() << "' -> '"
165-
<< trapPath.str().str() << "': " << ec.message() << "\n";
166-
}
167130
}
168131

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

swift/extractor/infra/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ load("//swift:rules.bzl", "swift_cc_library")
22

33
swift_cc_library(
44
name = "infra",
5+
srcs = glob(["*.cpp"]),
56
hdrs = glob(["*.h"]),
67
visibility = ["//swift:__subpackages__"],
78
deps = [
89
"//swift/extractor/trap",
10+
"//swift/tools/prebuilt:swift-llvm-support",
911
],
1012
)

swift/extractor/infra/SwiftDispatcher.h

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
#include <swift/Basic/SourceManager.h>
55
#include <llvm/Support/FileSystem.h>
66

7-
#include "swift/extractor/trap/TrapArena.h"
87
#include "swift/extractor/trap/TrapLabelStore.h"
9-
#include "swift/extractor/trap/TrapOutput.h"
8+
#include "swift/extractor/trap/TrapDomain.h"
109
#include "swift/extractor/infra/SwiftTagTraits.h"
1110
#include "swift/extractor/trap/generated/TrapClasses.h"
1211

@@ -22,12 +21,10 @@ class SwiftDispatcher {
2221
// all references and pointers passed as parameters to this constructor are supposed to outlive
2322
// the SwiftDispatcher
2423
SwiftDispatcher(const swift::SourceManager& sourceManager,
25-
TrapArena& arena,
26-
TrapOutput& trap,
24+
TrapDomain& trap,
2725
swift::ModuleDecl& currentModule,
2826
swift::SourceFile* currentPrimarySourceFile = nullptr)
2927
: sourceManager{sourceManager},
30-
arena{arena},
3128
trap{trap},
3229
currentModule{currentModule},
3330
currentPrimarySourceFile{currentPrimarySourceFile} {}
@@ -77,6 +74,7 @@ class SwiftDispatcher {
7774
}
7875
waitingForNewLabel = e;
7976
visit(e);
77+
// TODO when everything is moved to structured C++ classes, this should be moved to createEntry
8078
if (auto l = store.get(e)) {
8179
if constexpr (!std::is_base_of_v<swift::TypeBase, E>) {
8280
attachLocation(e, *l);
@@ -95,13 +93,17 @@ class SwiftDispatcher {
9593
return fetchLabelFromUnion<AstNodeTag>(node);
9694
}
9795

96+
TrapLabel<ConditionElementTag> fetchLabel(const swift::StmtConditionElement& element) {
97+
return fetchLabel(&element);
98+
}
99+
98100
// Due to the lazy emission approach, we must assign a label to a corresponding AST node before
99101
// it actually gets emitted to handle recursive cases such as recursive calls, or recursive type
100102
// declarations
101103
template <typename E, typename... Args>
102104
TrapLabelOf<E> assignNewLabel(E* e, Args&&... args) {
103105
assert(waitingForNewLabel == Store::Handle{e} && "assignNewLabel called on wrong entity");
104-
auto label = createLabel<TrapTagOf<E>>(std::forward<Args>(args)...);
106+
auto label = trap.createLabel<TrapTagOf<E>>(std::forward<Args>(args)...);
105107
store.insert(e, label);
106108
waitingForNewLabel = std::monostate{};
107109
return label;
@@ -118,18 +120,13 @@ class SwiftDispatcher {
118120
return TrapClassOf<E>{assignNewLabel(&e, std::forward<Args>(args)...)};
119121
}
120122

121-
template <typename Tag>
122-
TrapLabel<Tag> createLabel() {
123-
auto ret = arena.allocateLabel<Tag>();
124-
trap.assignStar(ret);
125-
return ret;
126-
}
127-
128-
template <typename Tag, typename... Args>
129-
TrapLabel<Tag> createLabel(Args&&... args) {
130-
auto ret = arena.allocateLabel<Tag>();
131-
trap.assignKey(ret, std::forward<Args>(args)...);
132-
return ret;
123+
// used to create a new entry for entities that should not be cached
124+
// an example is swift::Argument, that are created on the fly and thus have no stable pointer
125+
template <typename E, typename... Args, std::enable_if_t<!std::is_pointer_v<E>>* = nullptr>
126+
auto createUncachedEntry(const E& e, Args&&... args) {
127+
auto label = trap.createLabel<TrapTagOf<E>>(std::forward<Args>(args)...);
128+
attachLocation(&e, label);
129+
return TrapClassOf<E>{label};
133130
}
134131

135132
template <typename Locatable>
@@ -213,6 +210,7 @@ class SwiftDispatcher {
213210
using Store = TrapLabelStore<swift::Decl,
214211
swift::Stmt,
215212
swift::StmtCondition,
213+
swift::StmtConditionElement,
216214
swift::CaseLabelItem,
217215
swift::Expr,
218216
swift::Pattern,
@@ -227,13 +225,13 @@ class SwiftDispatcher {
227225
return;
228226
}
229227
std::string filepath = getFilepath(start);
230-
auto fileLabel = createLabel<FileTag>(filepath);
228+
auto fileLabel = trap.createLabel<FileTag>(filepath);
231229
// TODO: do not emit duplicate trap entries for Files
232230
trap.emit(FilesTrap{fileLabel, filepath});
233231
auto [startLine, startColumn] = sourceManager.getLineAndColumnInBuffer(start);
234232
auto [endLine, endColumn] = sourceManager.getLineAndColumnInBuffer(end);
235-
auto locLabel = createLabel<LocationTag>('{', fileLabel, "}:", startLine, ':', startColumn, ':',
236-
endLine, ':', endColumn);
233+
auto locLabel = trap.createLabel<LocationTag>('{', fileLabel, "}:", startLine, ':', startColumn,
234+
':', endLine, ':', endColumn);
237235
trap.emit(LocationsTrap{locLabel, fileLabel, startLine, startColumn, endLine, endColumn});
238236
trap.emit(LocatableLocationsTrap{locatableLabel, locLabel});
239237
}
@@ -275,16 +273,16 @@ class SwiftDispatcher {
275273
// which are to be introduced in follow-up PRs
276274
virtual void visit(swift::Decl* decl) = 0;
277275
virtual void visit(swift::Stmt* stmt) = 0;
278-
virtual void visit(swift::StmtCondition* cond) = 0;
276+
virtual void visit(const swift::StmtCondition* cond) = 0;
277+
virtual void visit(const swift::StmtConditionElement* cond) = 0;
279278
virtual void visit(swift::CaseLabelItem* item) = 0;
280279
virtual void visit(swift::Expr* expr) = 0;
281280
virtual void visit(swift::Pattern* pattern) = 0;
282281
virtual void visit(swift::TypeRepr* type) = 0;
283282
virtual void visit(swift::TypeBase* type) = 0;
284283

285284
const swift::SourceManager& sourceManager;
286-
TrapArena& arena;
287-
TrapOutput& trap;
285+
TrapDomain& trap;
288286
Store store;
289287
Store::Handle waitingForNewLabel{std::monostate{}};
290288
swift::ModuleDecl& currentModule;

swift/extractor/infra/SwiftTagTraits.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ using SILBoxTypeReprTag = SilBoxTypeReprTag;
3636

3737
MAP_TAG(Stmt);
3838
MAP_TAG(StmtCondition);
39+
MAP_TYPE_TO_TAG(StmtConditionElement, ConditionElementTag);
3940
MAP_TAG(CaseLabelItem);
4041
#define ABSTRACT_STMT(CLASS, PARENT) MAP_SUBTAG(CLASS##Stmt, PARENT)
4142
#define STMT(CLASS, PARENT) ABSTRACT_STMT(CLASS, PARENT)
4243
#include <swift/AST/StmtNodes.def>
4344

4445
MAP_TAG(Expr);
46+
MAP_TAG(Argument);
4547
#define ABSTRACT_EXPR(CLASS, PARENT) MAP_SUBTAG(CLASS##Expr, PARENT)
4648
#define EXPR(CLASS, PARENT) ABSTRACT_EXPR(CLASS, PARENT)
4749
#include <swift/AST/ExprNodes.def>

swift/extractor/infra/TargetFile.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include "swift/extractor/infra/TargetFile.h"
2+
3+
#include <iostream>
4+
#include <cstdio>
5+
#include <cerrno>
6+
#include <system_error>
7+
8+
#include <llvm/Support/FileSystem.h>
9+
#include <llvm/Support/Path.h>
10+
11+
namespace codeql {
12+
namespace {
13+
[[noreturn]] void error(const char* action, const std::string& arg, std::error_code ec) {
14+
std::cerr << "Unable to " << action << ": " << arg << " (" << ec.message() << ")\n";
15+
std::abort();
16+
}
17+
18+
[[noreturn]] void error(const char* action, const std::string& arg) {
19+
error(action, arg, {errno, std::system_category()});
20+
}
21+
22+
void ensureParentDir(const std::string& path) {
23+
auto parent = llvm::sys::path::parent_path(path);
24+
if (auto ec = llvm::sys::fs::create_directories(parent)) {
25+
error("create directory", parent.str(), ec);
26+
}
27+
}
28+
29+
std::string initPath(std::string_view target, std::string_view dir) {
30+
std::string ret{dir};
31+
assert(!target.empty() && "target must be a non-empty path");
32+
if (target[0] != '/') {
33+
ret += '/';
34+
}
35+
ret.append(target);
36+
ensureParentDir(ret);
37+
return ret;
38+
}
39+
} // namespace
40+
41+
TargetFile::TargetFile(std::string_view target,
42+
std::string_view targetDir,
43+
std::string_view workingDir)
44+
: workingPath{initPath(target, workingDir)}, targetPath{initPath(target, targetDir)} {}
45+
46+
bool TargetFile::init() {
47+
errno = 0;
48+
// since C++17 "x" mode opens with O_EXCL (fails if file already exists)
49+
if (auto f = std::fopen(targetPath.c_str(), "wx")) {
50+
std::fclose(f);
51+
out.open(workingPath);
52+
checkOutput("open file for writing");
53+
return true;
54+
}
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 ret;
67+
return std::nullopt;
68+
}
69+
70+
void TargetFile::commit() {
71+
if (out.is_open()) {
72+
out.close();
73+
errno = 0;
74+
if (std::rename(workingPath.c_str(), targetPath.c_str()) != 0) {
75+
error("rename file", targetPath);
76+
}
77+
}
78+
}
79+
80+
void TargetFile::checkOutput(const char* action) {
81+
if (!out) {
82+
error(action, workingPath);
83+
}
84+
}
85+
} // namespace codeql

0 commit comments

Comments
 (0)