Skip to content

Commit 62d36a2

Browse files
committed
Swift: Extract files
1 parent a5189ea commit 62d36a2

13 files changed

Lines changed: 233 additions & 12 deletions

File tree

swift/extractor/BUILD.bazel

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ alias(
88

99
cc_binary(
1010
name = "extractor",
11-
srcs = ["main.cpp"],
11+
srcs = [
12+
"Extractor.h",
13+
"Extractor.cpp",
14+
"Configuration.h",
15+
"main.cpp",
16+
],
17+
copts = [
18+
"-fno-rtti",
19+
],
1220
target_compatible_with = select({
1321
"@platforms//os:linux": [],
1422
"@platforms//os:macos": [],

swift/extractor/Configuration.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#ifndef SWIFT_EXTRACTOR_CONFIGURATION_H_
2+
#define SWIFT_EXTRACTOR_CONFIGURATION_H_
3+
4+
#include <string>
5+
#include <vector>
6+
7+
namespace codeql {
8+
struct Configuration {
9+
std::string trapDir;
10+
std::string sourceArchiveDir;
11+
std::vector<const char*> frontendOptions;
12+
};
13+
} // namespace codeql
14+
15+
#endif // SWIFT_EXTRACTOR_CONFIGURATION_H_

swift/extractor/Extractor.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include "Extractor.h"
2+
3+
#include <filesystem>
4+
#include <fstream>
5+
#include <iostream>
6+
#include <sstream>
7+
#include <memory>
8+
#include <unistd.h>
9+
10+
#include <swift/AST/SourceFile.h>
11+
#include <llvm/ADT/SmallString.h>
12+
#include <llvm/Support/FileSystem.h>
13+
#include <llvm/Support/Path.h>
14+
15+
using namespace codeql;
16+
17+
Extractor::Extractor(const Configuration& config, swift::CompilerInstance& instance)
18+
: config{config}, compiler{instance} {}
19+
20+
void Extractor::extract() {
21+
if (compiler.getPrimarySourceFiles().empty()) {
22+
swift::ModuleDecl* module = compiler.getMainModule();
23+
if (!module->getFiles().empty() &&
24+
module->getFiles().front()->getKind() == swift::FileUnitKind::Source) {
25+
/// We can only call getMainSourceFile if the first file is of a Source kind
26+
swift::SourceFile& file = module->getMainSourceFile();
27+
extractFile(file);
28+
}
29+
} else {
30+
for (auto s : compiler.getPrimarySourceFiles()) {
31+
extractFile(*s);
32+
}
33+
}
34+
}
35+
36+
void Extractor::extractFile(swift::SourceFile& file) {
37+
if (std::error_code ec = llvm::sys::fs::create_directories(config.trapDir)) {
38+
std::cerr << "Cannot create TRAP directory: " << ec.message() << "\n";
39+
return;
40+
}
41+
42+
if (std::error_code ec = llvm::sys::fs::create_directories(config.sourceArchiveDir)) {
43+
std::cerr << "Cannot create source archive directory: " << ec.message() << "\n";
44+
return;
45+
}
46+
47+
llvm::SmallString<PATH_MAX> srcFilePath(file.getFilename());
48+
llvm::sys::fs::make_absolute(srcFilePath);
49+
50+
llvm::SmallString<PATH_MAX> dstFilePath(config.sourceArchiveDir);
51+
llvm::sys::path::append(dstFilePath, srcFilePath);
52+
53+
llvm::StringRef parent = llvm::sys::path::parent_path(dstFilePath);
54+
if (std::error_code ec = llvm::sys::fs::create_directories(parent)) {
55+
std::cerr << "Cannot create source archive destination directory '" << parent.str()
56+
<< "': " << ec.message() << "\n";
57+
return;
58+
}
59+
60+
if (std::error_code ec = llvm::sys::fs::copy_file(srcFilePath, dstFilePath)) {
61+
std::cerr << "Cannot archive source file '" << srcFilePath.str().str() << "' -> '"
62+
<< dstFilePath.str().str() << "': " << ec.message() << "\n";
63+
return;
64+
}
65+
66+
/// TODO: find a more robust approach to avoid collisions?
67+
std::string tempTrapName = file.getFilename().str() + '.' + std::to_string(getpid()) + ".trap";
68+
llvm::SmallString<PATH_MAX> tempTrapPath(config.trapDir);
69+
llvm::sys::path::append(tempTrapPath, tempTrapName);
70+
71+
std::ofstream trap(tempTrapPath.str().str());
72+
if (!trap) {
73+
std::error_code ec;
74+
ec.assign(errno, std::generic_category());
75+
std::cerr << "Cannot create temp trap file '" << tempTrapPath.str().str()
76+
<< "': " << ec.message() << "\n";
77+
return;
78+
}
79+
std::stringstream ss;
80+
ss << "-frontend ";
81+
for (auto opt : config.frontendOptions) {
82+
ss << std::quoted(opt) << " ";
83+
}
84+
ss << "\n";
85+
trap << "// frontend-options: " << ss.str();
86+
87+
trap << "#0=*\n";
88+
trap << "files(#0, " << std::quoted(srcFilePath.str().str()) << ")\n";
89+
90+
/// TODO: Pick a better name to avoid collisions
91+
std::string trapName = file.getFilename().str() + ".trap";
92+
llvm::SmallString<PATH_MAX> trapPath(config.trapDir);
93+
llvm::sys::path::append(trapPath, trapName);
94+
95+
/// TODO: The last process wins. Should we do better than that?
96+
if (std::error_code ec = llvm::sys::fs::rename(tempTrapPath, trapPath)) {
97+
std::cerr << "Cannot rename temp trap file '" << tempTrapPath.str().str() << "' -> '"
98+
<< trapPath.str().str() << "': " << ec.message() << "\n";
99+
}
100+
}
101+

swift/extractor/Extractor.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#ifndef SWIFT_EXTRACTOR_H_
2+
#define SWIFT_EXTRACTOR_H_
3+
4+
#include "Configuration.h"
5+
#include <swift/AST/SourceFile.h>
6+
#include <swift/Frontend/Frontend.h>
7+
#include <memory>
8+
9+
namespace codeql {
10+
11+
class Extractor {
12+
public:
13+
explicit Extractor(const Configuration& config, swift::CompilerInstance& instance);
14+
void extract();
15+
16+
private:
17+
void extractFile(swift::SourceFile& file);
18+
19+
const Configuration& config;
20+
swift::CompilerInstance& compiler;
21+
};
22+
} // namespace codeql
23+
24+
#endif // SWIFT_EXTRACTOR_H_

swift/extractor/main.cpp

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,58 @@
33
#include <stdlib.h>
44

55
#include <swift/Basic/LLVMInitialize.h>
6+
#include <swift/FrontendTool/FrontendTool.h>
7+
8+
#include "Extractor.h"
9+
10+
using namespace std::string_literals;
11+
12+
class Observer : public swift::FrontendObserver {
13+
public:
14+
explicit Observer(const codeql::Configuration& config) : config{config} {}
15+
16+
void performedSemanticAnalysis(swift::CompilerInstance& instance) override {
17+
codeql::Extractor extractor{config, instance};
18+
extractor.extract();
19+
}
20+
21+
private:
22+
const codeql::Configuration& config;
23+
};
24+
25+
static std::string getenv_default(const char* envvar, const std::string& def) {
26+
if (const char* var = getenv(envvar)) {
27+
return var;
28+
}
29+
return def;
30+
}
631

732
int main(int argc, char** argv) {
33+
if (argc == 1) {
34+
/// TODO: print usage
35+
return 1;
36+
}
37+
/// The frontend can be called in different modes, we are only interested
38+
/// in -frontend mode
39+
/// TODO: filter out at the tracer level
40+
if ("-frontend"s != argv[1]) {
41+
return 0;
42+
}
843
PROGRAM_START(argc, argv);
9-
if (auto trapDir = getenv("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR")) {
10-
std::string file = trapDir;
11-
file += "/my_first.trap";
12-
if (std::ofstream out{file}) {
13-
out << "answer_to_life_the_universe_and_everything(42)\n";
44+
45+
codeql::Configuration configuration{};
46+
configuration.trapDir = getenv_default("CODEQL_EXTRACTOR_SWIFT_TRAP_DIR", ".");
47+
configuration.sourceArchiveDir = getenv_default("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", ".");
48+
std::vector<const char*> args;
49+
for (int i = 1; i < argc; i++) {
50+
if ("-frontend"s == argv[i]) {
51+
continue;
1452
}
53+
args.push_back(argv[i]);
1554
}
16-
return 0;
55+
configuration.frontendOptions = args;
56+
Observer observer(configuration);
57+
int frontend_rc = swift::performFrontend(args, "swift-extractor", (void*)main, &observer);
58+
llvm::llvm_shutdown();
59+
return frontend_rc;
1760
}

swift/ql/lib/swift.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/** Top-level import for the Swift language pack */
2+
3+
import swift.File

swift/ql/lib/swift/File.qll

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/** TODO: to be replaced */
2+
3+
/** A file. */
4+
class File extends @file {
5+
/** toString */
6+
string toString() { result = getAbsolutePath() }
7+
8+
/** Gets the name of this file. */
9+
string getName() { files(this, result) }
10+
11+
/** Gets the absolute path of this file. */
12+
string getAbsolutePath() { result = getName() }
13+
14+
/** Gets the full name of this file. */
15+
string getFullName() { result = getAbsolutePath() }
16+
17+
/** Gets the URL of this file. */
18+
string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
19+
20+
/** Gets the base name of this file. */
21+
string getBaseName() {
22+
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
23+
}
24+
}

swift/ql/test/answer.expected

Lines changed: 0 additions & 1 deletion
This file was deleted.

swift/ql/test/answer.ql

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| hello.swift:0:0:0:0 | hello.swift |

0 commit comments

Comments
 (0)