/** * Provides classes representing files and folders. */ import semmle.code.cpp.Element import semmle.code.cpp.Declaration import semmle.code.cpp.metrics.MetricFile /** A file or folder. */ class Container extends Locatable, @container { /** * Gets the absolute, canonical path of this container, using forward slashes * as path separator. * * The path starts with a _root prefix_ followed by zero or more _path * segments_ separated by forward slashes. * * The root prefix is of one of the following forms: * * 1. A single forward slash `/` (Unix-style) * 2. An upper-case drive letter followed by a colon and a forward slash, * such as `C:/` (Windows-style) * 3. Two forward slashes, a computer name, and then another forward slash, * such as `//FileServer/` (UNC-style) * * Path segments are never empty (that is, absolute paths never contain two * contiguous slashes, except as part of a UNC-style root prefix). Also, path * segments never contain forward slashes, and no path segment is of the * form `.` (one dot) or `..` (two dots). * * Note that an absolute path never ends with a forward slash, except if it is * a bare root prefix, that is, the path has no path segments. A container * whose absolute path has no segments is always a `Folder`, not a `File`. */ string getAbsolutePath() { none() } // overridden by subclasses /** * DEPRECATED: Use `getLocation` instead. * Gets a URL representing the location of this container. * * For more information see [Providing URLs](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls). */ deprecated string getURL() { none() } // overridden by subclasses /** * Gets the relative path of this file or folder from the root folder of the * analyzed source location. The relative path of the root folder itself is * the empty string. * * This has no result if the container is outside the source root, that is, * if the root folder is not a reflexive, transitive parent of this container. */ string getRelativePath() { exists(string absPath, string pref | absPath = this.getAbsolutePath() and sourceLocationPrefix(pref) | absPath = pref and result = "" or absPath = pref.regexpReplaceAll("/$", "") + "/" + result and not result.matches("/%") ) } /** * Gets the base name of this container including extension, that is, the last * segment of its absolute path, or the empty string if it has no segments. * * Here are some examples of absolute paths and the corresponding base names * (surrounded with quotes to avoid ambiguity): * * * * * * * * * *
Absolute pathBase name
"/tmp/tst.js""tst.js"
"C:/Program Files (x86)""Program Files (x86)"
"/"""
"C:/"""
"D:/"""
"//FileServer/"""
*/ string getBaseName() { result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) } /** * Gets the extension of this container, that is, the suffix of its base name * after the last dot character, if any. * * In particular, * * - if the name does not include a dot, there is no extension, so this * predicate has no result; * - if the name ends in a dot, the extension is the empty string; * - if the name contains multiple dots, the extension follows the last dot. * * Here are some examples of absolute paths and the corresponding extensions * (surrounded with quotes to avoid ambiguity): * * * * * * * * *
Absolute pathExtension
"/tmp/tst.js""js"
"/tmp/.classpath""classpath"
"/bin/bash"not defined
"/tmp/tst2."""
"/tmp/x.tar.gz""gz"
*/ string getExtension() { result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) } /** * Gets the stem of this container, that is, the prefix of its base name up to * (but not including) the last dot character if there is one, or the entire * base name if there is not. * * Here are some examples of absolute paths and the corresponding stems * (surrounded with quotes to avoid ambiguity): * * * * * * * * *
Absolute pathStem
"/tmp/tst.js""tst"
"/tmp/.classpath"""
"/bin/bash""bash"
"/tmp/tst2.""tst2"
"/tmp/x.tar.gz""x.tar"
*/ string getStem() { result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) } /** Gets the parent container of this file or folder, if any. */ Container getParentContainer() { containerparent(unresolveElement(result), underlyingElement(this)) } /** Gets a file or sub-folder in this container. */ Container getAChildContainer() { this = result.getParentContainer() } /** Gets a file in this container. */ File getAFile() { result = this.getAChildContainer() } /** Gets the file in this container that has the given `baseName`, if any. */ File getFile(string baseName) { result = this.getAFile() and result.getBaseName() = baseName } /** Gets a sub-folder in this container. */ Folder getAFolder() { result = this.getAChildContainer() } /** Gets the sub-folder in this container that has the given `baseName`, if any. */ Folder getFolder(string baseName) { result = this.getAFolder() and result.getBaseName() = baseName } /** * Gets a textual representation of the path of this container. * * This is the absolute path of the container. */ override string toString() { result = this.getAbsolutePath() } } /** * A folder that was observed on disk during the build process. * * For the example folder name of "/usr/home/me", the path decomposes to: * * 1. "/usr/home" - see `getParentContainer`. * 2. "me" - see `getBaseName`. * * To get the full path, use `getAbsolutePath`. */ class Folder extends Container, @folder { override string getAbsolutePath() { folders(underlyingElement(this), result) } override Location getLocation() { result.getContainer() = this and result.hasLocationInfo(_, 0, 0, 0, 0) } override string getAPrimaryQlClass() { result = "Folder" } /** * DEPRECATED: Use `getLocation` instead. * Gets the URL of this folder. */ deprecated override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" } /** * DEPRECATED: use `getAbsolutePath` instead. * Gets the name of this folder. */ deprecated string getName() { folders(underlyingElement(this), result) } /** * DEPRECATED: use `getBaseName` instead. * Gets the last part of the folder name. */ deprecated string getShortName() { result = this.getBaseName() } } /** * A file that was observed on disk during the build process. * * For the example filename of "/usr/home/me/myprogram.c", the filename * decomposes to: * * 1. "/usr/home/me" - see `getParentContainer`. * 2. "myprogram.c" - see `getBaseName`. * * The base name further decomposes into the _stem_ and _extension_ -- see * `getStem` and `getExtension`. To get the full path, use `getAbsolutePath`. */ class File extends Container, @file { override string getAbsolutePath() { files(underlyingElement(this), result) } override string toString() { result = Container.super.toString() } override string getAPrimaryQlClass() { result = "File" } override Location getLocation() { result.getContainer() = this and result.hasLocationInfo(_, 0, 0, 0, 0) } /** * DEPRECATED: Use `getLocation` instead. * Gets the URL of this file. */ deprecated override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" } /** Holds if this file was compiled as C (at any point). */ predicate compiledAsC() { fileannotations(underlyingElement(this), 1, "compiled as c", "1") } /** Holds if this file was compiled as C++ (at any point). */ predicate compiledAsCpp() { fileannotations(underlyingElement(this), 1, "compiled as c++", "1") } /** * Holds if this file was compiled by a Microsoft compiler (at any point). * * Note: currently unreliable - on some projects only some of the files that * are compiled by a Microsoft compiler are detected by this predicate. */ predicate compiledAsMicrosoft() { exists(File f, Compilation c | c.getAFileCompiled() = f and ( c.getAnArgument() = "--microsoft" or c.getAnArgument() .toLowerCase() .replaceAll("\\", "/") .matches(["%/cl.exe", "%/clang-cl.exe"]) ) and f.getAnIncludedFile*() = this ) } /** Gets a top-level element declared in this file. */ Declaration getATopLevelDeclaration() { result.getAFile() = this and result.isTopLevel() } /** Gets a declaration in this file. */ Declaration getADeclaration() { result.getAFile() = this } /** Holds if this file uses the given macro. */ predicate usesMacro(Macro m) { exists(MacroInvocation mi | mi.getFile() = this and mi.getMacro() = m ) } /** * Gets a file that is directly included from this file (using a * pre-processor directive like `#include`). */ File getAnIncludedFile() { exists(Include i | i.getFile() = this and i.getIncludedFile() = result) } /** * Holds if this file may be from source. This predicate holds for all files * except the dummy file, whose name is the empty string, which contains * declarations that are built into the compiler. */ override predicate fromSource() { numlines(underlyingElement(this), _, _, _) } /** Gets the metric file. */ MetricFile getMetrics() { result = this } /** * Gets the remainder of the base name after the first dot character. Note * that the name of this predicate is in plural form, unlike `getExtension`, * which gets the remainder of the base name after the _last_ dot character. * * Predicates `getStem` and `getExtension` should be preferred over * `getShortName` and `getExtensions` since the former pair is compatible * with the file libraries of other languages. * Note the slight difference between this predicate and `getStem`: * for example, for "file.tar.gz", this predicate will have the result * "tar.gz", while `getExtension` will have the result "gz". */ string getExtensions() { exists(string name, int firstDotPos | name = this.getBaseName() and firstDotPos = min([name.indexOf("."), name.length() - 1]) and result = name.suffix(firstDotPos + 1) ) } /** * Gets the short name of this file, that is, the prefix of its base name up * to (but not including) the first dot character if there is one, or the * entire base name if there is not. For example, if the full name is * "/path/to/filename.a.bcd" then the short name is "filename". * * Predicates `getStem` and `getExtension` should be preferred over * `getShortName` and `getExtensions` since the former pair is compatible * with the file libraries of other languages. * Note the slight difference between this predicate and `getStem`: * for example, for "file.tar.gz", this predicate will have the result * "file", while `getStem` will have the result "file.tar". */ string getShortName() { exists(string name, int firstDotPos | name = this.getBaseName() and firstDotPos = min([name.indexOf("."), name.length()]) and result = name.prefix(firstDotPos) ) or this.getAbsolutePath() = "" and result = "" } } /** * Holds if any file was compiled by a Microsoft compiler. */ predicate anyFileCompiledAsMicrosoft() { any(File f).compiledAsMicrosoft() } /** * A C/C++ header file, as determined (mainly) by file extension. * * For the related notion of whether a file is included anywhere (using a * pre-processor directive like `#include`), use `Include.getIncludedFile`. */ class HeaderFile extends File { HeaderFile() { this.getExtension().toLowerCase() = ["h", "r", "hpp", "hxx", "h++", "hh", "hp", "tcc", "tpp", "txx", "t++"] or not exists(this.getExtension()) and exists(Include i | i.getIncludedFile() = this) } override string getAPrimaryQlClass() { result = "HeaderFile" } /** * Holds if this header file does not contain any declaration entries or top level * declarations. For example it might be: * - a file containing only preprocessor directives and/or comments * - an empty file * - a file that contains non-top level code or data that's included in an * unusual way */ predicate noTopLevelCode() { not exists(DeclarationEntry de | de.getFile() = this) and not exists(Declaration d | d.getFile() = this and d.isTopLevel()) and not exists(UsingEntry ue | ue.getFile() = this) } } /** * A C source file, as determined by file extension. * * For the related notion of whether a file is compiled as C code, use * `File.compiledAsC`. */ class CFile extends File { CFile() { this.getExtension().toLowerCase() = ["c", "i"] } override string getAPrimaryQlClass() { result = "CFile" } } /** * A C++ source file, as determined by file extension. * * For the related notion of whether a file is compiled as C++ code, use * `File.compiledAsCpp`. */ class CppFile extends File { CppFile() { this.getExtension().toLowerCase() = ["cpp", "cxx", "c++", "cc", "cp", "icc", "ipp", "ixx", "i++", "ii"] // Note: .C files are indistinguishable from .c files on some // file systems, so we just treat them as CFile's. } override string getAPrimaryQlClass() { result = "CppFile" } }