|
| 1 | +/** |
| 2 | + * Provides classes for modeling namespaces, `using` directives and `using` declarations. |
| 3 | + */ |
| 4 | + |
| 5 | +import semmle.code.cpp.Element |
| 6 | +import semmle.code.cpp.Type |
| 7 | +import semmle.code.cpp.metrics.MetricNamespace |
| 8 | + |
| 9 | +/** |
| 10 | + * A C++ namespace. For example the (single) namespace `A` in the following |
| 11 | + * code: |
| 12 | + * ``` |
| 13 | + * namespace A |
| 14 | + * { |
| 15 | + * // ... |
| 16 | + * } |
| 17 | + * |
| 18 | + * // ... |
| 19 | + * |
| 20 | + * namespace A |
| 21 | + * { |
| 22 | + * // ... |
| 23 | + * } |
| 24 | + * ``` |
| 25 | + * Note that namespaces are somewhat nebulous entities, as they do not in |
| 26 | + * general have a single well-defined location in the source code. The |
| 27 | + * related notion of a `NamespaceDeclarationEntry` is rather more concrete, |
| 28 | + * and should be used when a location is required. For example, the `std::` |
| 29 | + * namespace is particularly nebulous, as parts of it are defined across a |
| 30 | + * wide range of headers. As a more extreme example, the global namespace |
| 31 | + * is never explicitly declared, but might correspond to a large proportion |
| 32 | + * of the source code. |
| 33 | + */ |
| 34 | +class Namespace extends NameQualifyingElement, @namespace { |
| 35 | + /** |
| 36 | + * Gets the location of the namespace. Most namespaces do not have a |
| 37 | + * single well-defined source location, so a dummy location is returned, |
| 38 | + * unless the namespace has exactly one declaration entry. |
| 39 | + */ |
| 40 | + override Location getLocation() { |
| 41 | + if strictcount(this.getADeclarationEntry()) = 1 |
| 42 | + then result = this.getADeclarationEntry().getLocation() |
| 43 | + else result instanceof UnknownDefaultLocation |
| 44 | + } |
| 45 | + |
| 46 | + /** Gets the simple name of this namespace. */ |
| 47 | + override string getName() { namespaces(underlyingElement(this), result) } |
| 48 | + |
| 49 | + /** Holds if this element is named `name`. */ |
| 50 | + predicate hasName(string name) { name = this.getName() } |
| 51 | + |
| 52 | + /** Holds if this namespace is anonymous. */ |
| 53 | + predicate isAnonymous() { this.hasName("(unnamed namespace)") } |
| 54 | + |
| 55 | + /** Gets the name of the parent namespace, if it exists. */ |
| 56 | + private string getParentName() { |
| 57 | + result = this.getParentNamespace().getName() and |
| 58 | + result != "" |
| 59 | + } |
| 60 | + |
| 61 | + /** Gets the qualified name of this namespace. For example: `a::b`. */ |
| 62 | + string getQualifiedName() { |
| 63 | + if exists(this.getParentName()) |
| 64 | + then result = this.getParentNamespace().getQualifiedName() + "::" + this.getName() |
| 65 | + else result = this.getName() |
| 66 | + } |
| 67 | + |
| 68 | + /** Gets the parent namespace, if any. */ |
| 69 | + Namespace getParentNamespace() { |
| 70 | + namespacembrs(unresolveElement(result), underlyingElement(this)) |
| 71 | + or |
| 72 | + not namespacembrs(_, underlyingElement(this)) and result instanceof GlobalNamespace |
| 73 | + } |
| 74 | + |
| 75 | + /** Gets a child declaration of this namespace. */ |
| 76 | + Declaration getADeclaration() { namespacembrs(underlyingElement(this), unresolveElement(result)) } |
| 77 | + |
| 78 | + /** Gets a child namespace of this namespace. */ |
| 79 | + Namespace getAChildNamespace() { |
| 80 | + namespacembrs(underlyingElement(this), unresolveElement(result)) |
| 81 | + } |
| 82 | + |
| 83 | + /** Holds if the namespace is inline. */ |
| 84 | + predicate isInline() { namespace_inline(underlyingElement(this)) } |
| 85 | + |
| 86 | + /** Holds if this namespace may be from source. */ |
| 87 | + override predicate fromSource() { this.getADeclaration().fromSource() } |
| 88 | + |
| 89 | + /** Gets the metric namespace. */ |
| 90 | + MetricNamespace getMetrics() { result = this } |
| 91 | + |
| 92 | + /** Gets a version of the `QualifiedName` that is more suitable for display purposes. */ |
| 93 | + string getFriendlyName() { result = this.getQualifiedName() } |
| 94 | + |
| 95 | + final override string toString() { result = this.getFriendlyName() } |
| 96 | + |
| 97 | + /** Gets a declaration of (part of) this namespace. */ |
| 98 | + NamespaceDeclarationEntry getADeclarationEntry() { result.getNamespace() = this } |
| 99 | + |
| 100 | + /** Gets a file which declares (part of) this namespace. */ |
| 101 | + File getAFile() { result = this.getADeclarationEntry().getLocation().getFile() } |
| 102 | +} |
| 103 | + |
| 104 | +/** |
| 105 | + * A declaration of (part of) a C++ namespace. This corresponds to a single |
| 106 | + * `namespace N { ... }` occurrence in the source code. For example the two |
| 107 | + * mentions of `A` in the following code: |
| 108 | + * ``` |
| 109 | + * namespace A |
| 110 | + * { |
| 111 | + * // ... |
| 112 | + * } |
| 113 | + * |
| 114 | + * // ... |
| 115 | + * |
| 116 | + * namespace A |
| 117 | + * { |
| 118 | + * // ... |
| 119 | + * } |
| 120 | + * ``` |
| 121 | + */ |
| 122 | +class NamespaceDeclarationEntry extends Locatable, @namespace_decl { |
| 123 | + /** |
| 124 | + * Get the namespace that this declaration entry corresponds to. There |
| 125 | + * is a one-to-many relationship between `Namespace` and |
| 126 | + * `NamespaceDeclarationEntry`. |
| 127 | + */ |
| 128 | + Namespace getNamespace() { |
| 129 | + namespace_decls(underlyingElement(this), unresolveElement(result), _, _) |
| 130 | + } |
| 131 | + |
| 132 | + override string toString() { result = this.getNamespace().getFriendlyName() } |
| 133 | + |
| 134 | + /** |
| 135 | + * Gets the location of the token preceding the namespace declaration |
| 136 | + * entry's body. |
| 137 | + * |
| 138 | + * For named declarations, such as "namespace MyStuff { ... }", this will |
| 139 | + * give the "MyStuff" token. |
| 140 | + * |
| 141 | + * For anonymous declarations, such as "namespace { ... }", this will |
| 142 | + * give the "namespace" token. |
| 143 | + */ |
| 144 | + override Location getLocation() { namespace_decls(underlyingElement(this), _, result, _) } |
| 145 | + |
| 146 | + /** |
| 147 | + * Gets the location of the namespace declaration entry's body. For |
| 148 | + * example: the "{ ... }" in "namespace N { ... }". |
| 149 | + */ |
| 150 | + Location getBodyLocation() { namespace_decls(underlyingElement(this), _, _, result) } |
| 151 | + |
| 152 | + override string getAPrimaryQlClass() { result = "NamespaceDeclarationEntry" } |
| 153 | +} |
| 154 | + |
| 155 | +/** |
| 156 | + * A C++ `using` directive or `using` declaration. |
| 157 | + */ |
| 158 | +class UsingEntry extends Locatable, @using { |
| 159 | + override Location getLocation() { usings(underlyingElement(this), _, result) } |
| 160 | +} |
| 161 | + |
| 162 | +/** |
| 163 | + * A C++ `using` declaration. For example: |
| 164 | + * ``` |
| 165 | + * using std::string; |
| 166 | + * ``` |
| 167 | + */ |
| 168 | +class UsingDeclarationEntry extends UsingEntry { |
| 169 | + UsingDeclarationEntry() { |
| 170 | + not exists(Namespace n | usings(underlyingElement(this), unresolveElement(n), _)) |
| 171 | + } |
| 172 | + |
| 173 | + /** |
| 174 | + * Gets the declaration that is referenced by this using declaration. For |
| 175 | + * example, `std::string` in `using std::string`. |
| 176 | + */ |
| 177 | + Declaration getDeclaration() { usings(underlyingElement(this), unresolveElement(result), _) } |
| 178 | + |
| 179 | + override string toString() { result = "using " + this.getDeclaration().getDescription() } |
| 180 | +} |
| 181 | + |
| 182 | +/** |
| 183 | + * A C++ `using` directive. For example: |
| 184 | + * ``` |
| 185 | + * using namespace std; |
| 186 | + * ``` |
| 187 | + */ |
| 188 | +class UsingDirectiveEntry extends UsingEntry { |
| 189 | + UsingDirectiveEntry() { |
| 190 | + exists(Namespace n | usings(underlyingElement(this), unresolveElement(n), _)) |
| 191 | + } |
| 192 | + |
| 193 | + /** |
| 194 | + * Gets the namespace that is referenced by this using directive. For |
| 195 | + * example, `std` in `using namespace std`. |
| 196 | + */ |
| 197 | + Namespace getNamespace() { usings(underlyingElement(this), unresolveElement(result), _) } |
| 198 | + |
| 199 | + override string toString() { result = "using namespace " + this.getNamespace().getFriendlyName() } |
| 200 | +} |
| 201 | + |
| 202 | +/** |
| 203 | + * Holds if `g` is an instance of `GlobalNamespace`. This predicate |
| 204 | + * is used suppress a warning in `GlobalNamespace.getADeclaration()` |
| 205 | + * by providing a fake use of `this`. |
| 206 | + */ |
| 207 | +private predicate suppressWarningForUnused(GlobalNamespace g) { any() } |
| 208 | + |
| 209 | +/** |
| 210 | + * The C/C++ global namespace. |
| 211 | + */ |
| 212 | +class GlobalNamespace extends Namespace { |
| 213 | + GlobalNamespace() { this.hasName("") } |
| 214 | + |
| 215 | + override Declaration getADeclaration() { |
| 216 | + suppressWarningForUnused(this) and |
| 217 | + result.isTopLevel() and |
| 218 | + not namespacembrs(_, unresolveElement(result)) |
| 219 | + } |
| 220 | + |
| 221 | + /** Gets a child namespace of the global namespace. */ |
| 222 | + override Namespace getAChildNamespace() { |
| 223 | + suppressWarningForUnused(this) and |
| 224 | + not namespacembrs(unresolveElement(result), _) |
| 225 | + } |
| 226 | + |
| 227 | + override Namespace getParentNamespace() { none() } |
| 228 | + |
| 229 | + override string getFriendlyName() { result = "(global namespace)" } |
| 230 | +} |
| 231 | + |
| 232 | +/** |
| 233 | + * The C++ `std::` namespace. |
| 234 | + */ |
| 235 | +class StdNamespace extends Namespace { |
| 236 | + StdNamespace() { this.hasName("std") and this.getParentNamespace() instanceof GlobalNamespace } |
| 237 | +} |
0 commit comments