#pragma once #include #include #include #include "swift/extractor/trap/TrapLabelStore.h" #include "swift/extractor/trap/TrapDomain.h" #include "swift/extractor/infra/SwiftTagTraits.h" #include "swift/extractor/trap/generated/TrapClasses.h" namespace codeql { // The main responsibilities of the SwiftDispatcher are as follows: // * redirect specific AST node emission to a corresponding visitor (statements, expressions, etc.) // * storing TRAP labels for emitted AST nodes (in the TrapLabelStore) to avoid re-emission // Since SwiftDispatcher sees all the AST nodes, it also attaches a location to every 'locatable' // node (AST nodes that are not types: declarations, statements, expressions, etc.). class SwiftDispatcher { public: // all references and pointers passed as parameters to this constructor are supposed to outlive // the SwiftDispatcher SwiftDispatcher(const swift::SourceManager& sourceManager, TrapDomain& trap, swift::ModuleDecl& currentModule, swift::SourceFile* currentPrimarySourceFile = nullptr) : sourceManager{sourceManager}, trap{trap}, currentModule{currentModule}, currentPrimarySourceFile{currentPrimarySourceFile} {} template void emit(const Entry& entry) { trap.emit(entry); } template void emit(const std::optional& entry) { if (entry) { emit(*entry); } } template void emit(const std::variant& entry) { std::visit([this](const auto& e) { this->emit(e); }, entry); } // This is a helper method to emit TRAP entries for AST nodes that we don't fully support yet. template void emitUnknown(E* entity) { auto label = assignNewLabel(entity); using Trap = BindingTrapOf; static_assert(sizeof(Trap) == sizeof(label), "Binding traps of unknown entities must only have the `id` field (the class " "should be empty in schema.yml)"); emit(Trap{label}); emit(ElementIsUnknownTrap{label}); } // This method gives a TRAP label for already emitted AST node. // If the AST node was not emitted yet, then the emission is dispatched to a corresponding // visitor (see `visit(T *)` methods below). template TrapLabelOf fetchLabel(E* e, Args&&... args) { assert(e && "trying to fetch a label on nullptr, maybe fetchOptionalLabel is to be used?"); // this is required so we avoid any recursive loop: a `fetchLabel` during the visit of `e` might // end up calling `fetchLabel` on `e` itself, so we want the visit of `e` to call `fetchLabel` // only after having called `assignNewLabel` on `e`. assert(std::holds_alternative(waitingForNewLabel) && "fetchLabel called before assignNewLabel"); if (auto l = store.get(e)) { return *l; } waitingForNewLabel = e; visit(e, std::forward(args)...); // TODO when everything is moved to structured C++ classes, this should be moved to createEntry if (auto l = store.get(e)) { if constexpr (!std::is_base_of_v) { attachLocation(e, *l); } return *l; } assert(!"assignNewLabel not called during visit"); return {}; } // convenience `fetchLabel` overload for `swift::Type` (which is just a wrapper for // `swift::TypeBase*`) TrapLabel fetchLabel(swift::Type t) { return fetchLabel(t.getPointer()); } TrapLabel fetchLabel(swift::ASTNode node) { return fetchLabelFromUnion(node); } TrapLabel fetchLabel(const swift::IfConfigClause& clause) { return fetchLabel(&clause); } TrapLabel fetchLabel(const swift::StmtConditionElement& element) { return fetchLabel(&element); } // Due to the lazy emission approach, we must assign a label to a corresponding AST node before // it actually gets emitted to handle recursive cases such as recursive calls, or recursive type // declarations template TrapLabelOf assignNewLabel(E* e, Args&&... args) { assert(waitingForNewLabel == Store::Handle{e} && "assignNewLabel called on wrong entity"); auto label = trap.createLabel>(std::forward(args)...); store.insert(e, label); waitingForNewLabel = std::monostate{}; return label; } template >* = nullptr> TrapLabelOf assignNewLabel(const E& e, Args&&... args) { return assignNewLabel(&e, std::forward(args)...); } // convenience methods for structured C++ creation template >* = nullptr> auto createEntry(const E& e, Args&&... args) { return TrapClassOf{assignNewLabel(&e, std::forward(args)...)}; } // used to create a new entry for entities that should not be cached // an example is swift::Argument, that are created on the fly and thus have no stable pointer template >* = nullptr> auto createUncachedEntry(const E& e, Args&&... args) { auto label = trap.createLabel>(std::forward(args)...); attachLocation(&e, label); return TrapClassOf{label}; } template void attachLocation(Locatable locatable, TrapLabel locatableLabel) { attachLocation(&locatable, locatableLabel); } // Emits a Location TRAP entry and attaches it to a `Locatable` trap label template void attachLocation(Locatable* locatable, TrapLabel locatableLabel) { attachLocation(locatable->getStartLoc(), locatable->getEndLoc(), locatableLabel); } void attachLocation(const swift::IfConfigClause* clause, TrapLabel locatableLabel) { attachLocation(clause->Loc, clause->Loc, locatableLabel); } // Emits a Location TRAP entry and attaches it to a `Locatable` trap label for a given `SourceLoc` void attachLocation(swift::SourceLoc loc, TrapLabel locatableLabel) { attachLocation(loc, loc, locatableLabel); } // Emits a Location TRAP entry for a list of swift entities and attaches it to a `Locatable` trap // label template void attachLocation(llvm::MutableArrayRef* locatables, TrapLabel locatableLabel) { if (locatables->empty()) { return; } attachLocation(locatables->front().getStartLoc(), locatables->back().getEndLoc(), locatableLabel); } // return `std::optional(fetchLabel(arg))` if arg converts to true, otherwise std::nullopt // universal reference `Arg&&` is used to catch both temporary and non-const references, not // for perfect forwarding template auto fetchOptionalLabel(Arg&& arg, Args&&... args) -> std::optional { if (arg) { return fetchLabel(arg, std::forward(args)...); } return std::nullopt; } // map `fetchLabel` on the iterable `arg`, returning a vector of all labels // universal reference `Arg&&` is used to catch both temporary and non-const references, not // for perfect forwarding template auto fetchRepeatedLabels(Iterable&& arg) { std::vector ret; ret.reserve(arg.size()); for (auto&& e : arg) { ret.push_back(fetchLabel(e)); } return ret; } template void emitDebugInfo(const Args&... args) { trap.debug(std::forward(args)...); } // In order to not emit duplicated entries for declarations, we restrict emission to only // Decls declared within the current "scope". // Depending on the whether we are extracting a primary source file or not the scope is defined as // follows: // - not extracting a primary source file (`currentPrimarySourceFile == nullptr`): the current // scope means the current module. This is used in the case of system or builtin modules. // - extracting a primary source file: in this mode, we extract several files belonging to the // same module one by one. In this mode, we restrict emission only to the same file ignoring // all the other files. bool shouldEmitDeclBody(const swift::Decl& decl) { if (decl.getModuleContext() != ¤tModule) { return false; } // ModuleDecl is a special case: if it passed the previous test, it is the current module // but it never has a source file, so we short circuit to emit it in any case if (!currentPrimarySourceFile || decl.getKind() == swift::DeclKind::Module) { return true; } if (auto context = decl.getDeclContext()) { return currentPrimarySourceFile == context->getParentSourceFile(); } return false; } private: // types to be supported by assignNewLabel/fetchLabel need to be listed here using Store = TrapLabelStore; void attachLocation(swift::SourceLoc start, swift::SourceLoc end, TrapLabel locatableLabel) { if (!start.isValid() || !end.isValid()) { // invalid locations seem to come from entities synthesized by the compiler return; } std::string filepath = getFilepath(start); auto fileLabel = trap.createLabel(filepath); // TODO: do not emit duplicate trap entries for Files trap.emit(FilesTrap{fileLabel, filepath}); auto [startLine, startColumn] = sourceManager.getLineAndColumnInBuffer(start); auto [endLine, endColumn] = sourceManager.getLineAndColumnInBuffer(end); auto locLabel = trap.createLabel('{', fileLabel, "}:", startLine, ':', startColumn, ':', endLine, ':', endColumn); trap.emit(LocationsTrap{locLabel, fileLabel, startLine, startColumn, endLine, endColumn}); trap.emit(LocatableLocationsTrap{locatableLabel, locLabel}); } template TrapLabel fetchLabelFromUnion(const llvm::PointerUnion u) { TrapLabel ret{}; // with logical op short-circuiting, this will stop trying on the first successful fetch // don't feel tempted to replace the variable with the expression inside the `assert`, or // building with `NDEBUG` will not trigger the fetching bool unionCaseFound = (... || fetchLabelFromUnionCase(u, ret)); assert(unionCaseFound && "llvm::PointerUnion not set to a known case"); return ret; } template bool fetchLabelFromUnionCase(const llvm::PointerUnion u, TrapLabel& output) { // we rely on the fact that when we extract `ASTNode` instances (which only happens // on `BraceStmt` elements), we cannot encounter a standalone `TypeRepr` there, so we skip // this case; extracting `TypeRepr`s here would be problematic as we would not be able to // provide the corresponding type if constexpr (!std::is_same_v) { if (auto e = u.template dyn_cast()) { output = fetchLabel(e); return true; } } return false; } std::string getFilepath(swift::SourceLoc loc) { // TODO: this needs more testing // TODO: check canonicaliztion of names on a case insensitive filesystems // TODO: make symlink resolution conditional on CODEQL_PRESERVE_SYMLINKS=true auto displayName = sourceManager.getDisplayNameForLoc(loc); llvm::SmallString realPath; if (std::error_code ec = llvm::sys::fs::real_path(displayName, realPath)) { std::cerr << "Cannot get real path: '" << displayName.str() << "': " << ec.message() << "\n"; return {}; } return realPath.str().str(); } // TODO: for const correctness these should consistently be `const` (and maybe const references // as we don't expect `nullptr` here. However `swift::ASTVisitor` and `swift::TypeVisitor` do not // accept const pointers virtual void visit(swift::Decl* decl) = 0; virtual void visit(const swift::IfConfigClause* clause) = 0; virtual void visit(swift::Stmt* stmt) = 0; virtual void visit(const swift::StmtCondition* cond) = 0; virtual void visit(const swift::StmtConditionElement* cond) = 0; virtual void visit(swift::CaseLabelItem* item) = 0; virtual void visit(swift::Expr* expr) = 0; virtual void visit(swift::Pattern* pattern) = 0; virtual void visit(swift::TypeRepr* typeRepr, swift::Type type) = 0; virtual void visit(swift::TypeBase* type) = 0; const swift::SourceManager& sourceManager; TrapDomain& trap; Store store; Store::Handle waitingForNewLabel{std::monostate{}}; swift::ModuleDecl& currentModule; swift::SourceFile* currentPrimarySourceFile; }; } // namespace codeql