#pragma once #include "swift/extractor/trap/TrapArena.h" #include "swift/extractor/trap/TrapLabelStore.h" #include "swift/extractor/trap/generated/TrapClasses.h" #include "swift/extractor/SwiftTagTraits.h" #include #include #include namespace codeql { namespace detail { // The following `getKindName`s are used within "TBD" TRAP entries to visually mark an AST node as // not properly emitted yet. // TODO: To be replaced with QL counterpart template inline std::string getKindName(Kind kind) { return Parent::getKindName(kind).str(); } template <> inline std::string getKindName(swift::TypeKind kind) { switch (kind) { #define TYPE(CLASS, PARENT) \ case swift::TypeKind::CLASS: \ return #CLASS; #include "swift/AST/TypeNodes.def" default: return "Unknown"; } } template <> std::string inline getKindName(swift::TypeReprKind kind) { switch (kind) { #define TYPEREPR(CLASS, PARENT) \ case swift::TypeReprKind::CLASS: \ return #CLASS; #include "swift/AST/TypeReprNodes.def" default: return "Unknown"; } } } // namespace detail // 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: // sourceManager, arena, and trap are supposed to outlive the SwiftDispatcher SwiftDispatcher(const swift::SourceManager& sourceManager, TrapArena& arena, TrapOutput& trap) : sourceManager{sourceManager}, arena{arena}, trap{trap} {} // This is a helper method to emit TRAP entries for AST nodes that we don't fully support yet. template void TBD(Child* entity, const std::string& suffix) { using namespace std::string_literals; auto label = assignNewLabel(entity); auto kind = detail::getKindName(static_cast(entity)->getKind()); auto name = "TBD ("s + kind + suffix + ")"; if constexpr (std::is_same_v) { trap.emit(UnknownTypesTrap{label, name}); } else { trap.emit(UnknownAstNodesTrap{label, name}); } } private: // types to be supported by assignNewLabel/fetchLabel need to be listed here using Store = TrapLabelStore; // 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) { // 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(holds_alternative(waitingForNewLabel) && "fetchLabel called before assignNewLabel"); if (auto l = store.get(e)) { return *l; } waitingForNewLabel = e; visit(e); 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 {}; } // 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) { assert(waitingForNewLabel == Store::Handle{e} && "assignNewLabel called on wrong entity"); auto label = getLabel>(); trap.assignStar(label); store.insert(e, label); waitingForNewLabel = std::monostate{}; return label; } template TrapLabel getLabel() { return arena.allocateLabel(); } template void attachLocation(Locatable locatable, TrapLabel locatableLabel) { attachLocation(&locatable, locatableLabel); } // Emits a Location TRAP entry and attaches it to an AST node template void attachLocation(Locatable* locatable, TrapLabel locatableLabel) { auto start = locatable->getStartLoc(); auto end = locatable->getEndLoc(); if (!start.isValid() || !end.isValid()) { // invalid locations seem to come from entities synthesized by the compiler return; } std::string filepath = getFilepath(start); auto fileLabel = arena.allocateLabel(); trap.assignKey(fileLabel, 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 = arena.allocateLabel(); trap.assignKey(locLabel, '{', fileLabel, "}:", startLine, ':', startColumn, ':', endLine, ':', endColumn); trap.emit(LocationsTrap{locLabel, fileLabel, startLine, startColumn, endLine, endColumn}); trap.emit(LocatablesTrap{locatableLabel, locLabel}); } 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: The following methods are supposed to redirect TRAP emission to correpsonding visitors, // which are to be introduced in follow-up PRs virtual void visit(swift::Decl* decl) = 0; virtual void visit(swift::Stmt* stmt) = 0; virtual void visit(swift::Expr* expr) = 0; virtual void visit(swift::Pattern* pattern) = 0; virtual void visit(swift::TypeRepr* type) = 0; virtual void visit(swift::TypeBase* type) = 0; const swift::SourceManager& sourceManager; TrapArena& arena; TrapOutput& trap; Store store; Store::Handle waitingForNewLabel{std::monostate{}}; }; } // namespace codeql