|
| 1 | +/** Provides classes and predicates related to handling APIs for the VS Code extension. */ |
| 2 | + |
| 3 | +private import python |
| 4 | +private import semmle.python.frameworks.data.ModelsAsData |
| 5 | +private import semmle.python.frameworks.data.internal.ApiGraphModelsExtensions |
| 6 | +private import semmle.python.dataflow.new.internal.DataFlowDispatch as DP |
| 7 | +private import Util as Util |
| 8 | + |
| 9 | +/** |
| 10 | + * An string describing the kind of source code element being modeled. |
| 11 | + * |
| 12 | + * See `EndPoint`. |
| 13 | + */ |
| 14 | +class EndpointKind extends string { |
| 15 | + EndpointKind() { |
| 16 | + this in ["Function", "InstanceMethod", "ClassMethod", "StaticMethod", "InitMethod", "Class"] |
| 17 | + } |
| 18 | +} |
| 19 | + |
| 20 | +/** |
| 21 | + * An element of the source code to be modeled. |
| 22 | + * |
| 23 | + * See `EndPointKind` for the possible kinds of elements. |
| 24 | + */ |
| 25 | +abstract class Endpoint instanceof Util::RelevantScope { |
| 26 | + string namespace; |
| 27 | + string type; |
| 28 | + string name; |
| 29 | + |
| 30 | + Endpoint() { |
| 31 | + exists(string scopePath, string path, int pathIndex | |
| 32 | + scopePath = Util::computeScopePath(this) and |
| 33 | + pathIndex = scopePath.indexOf(".", 0, 0) |
| 34 | + | |
| 35 | + namespace = scopePath.prefix(pathIndex) and |
| 36 | + path = scopePath.suffix(pathIndex + 1) and |
| 37 | + ( |
| 38 | + exists(int nameIndex | nameIndex = max(path.indexOf(".")) | |
| 39 | + type = path.prefix(nameIndex) and |
| 40 | + name = path.suffix(nameIndex + 1) |
| 41 | + ) |
| 42 | + or |
| 43 | + not exists(path.indexOf(".")) and |
| 44 | + type = "" and |
| 45 | + name = path |
| 46 | + ) |
| 47 | + ) |
| 48 | + } |
| 49 | + |
| 50 | + /** Gets the namespace for this endpoint. This will typically be the package in which it is found. */ |
| 51 | + string getNamespace() { result = namespace } |
| 52 | + |
| 53 | + /** Gets hte basename of the file where this endpoint is found. */ |
| 54 | + string getFileName() { result = super.getLocation().getFile().getBaseName() } |
| 55 | + |
| 56 | + /** Gets a string representation of this endpoint. */ |
| 57 | + string toString() { result = super.toString() } |
| 58 | + |
| 59 | + /** Gets the location of this endpoint. */ |
| 60 | + Location getLocation() { result = super.getLocation() } |
| 61 | + |
| 62 | + /** Gets the name of the class in which this endpoint is found, or the empty string if it is not found inside a class. */ |
| 63 | + string getClass() { result = type } |
| 64 | + |
| 65 | + /** |
| 66 | + * Gets the name of the endpoint if it is not a class, or the empty string if it is a class |
| 67 | + * |
| 68 | + * If this endpoint is a class, the class name can be obtained via `getType`. |
| 69 | + */ |
| 70 | + string getFunctionName() { result = name } |
| 71 | + |
| 72 | + /** |
| 73 | + * Gets a string representation of the parameters of this endpoint. |
| 74 | + * |
| 75 | + * The string follows a specific format: |
| 76 | + * - Normal parameters(where arguments can be passed as either positional or keyword) are listed in order, separated by commas. |
| 77 | + * - Keyword-only parameters are listed in order, separated by commas, each followed by a colon. |
| 78 | + * - In the future, positional-only parameters will be listed in order, separated by commas, each followed by a slash. |
| 79 | + */ |
| 80 | + abstract string getParameters(); |
| 81 | + |
| 82 | + /** |
| 83 | + * Gets a boolean that is true iff this endpoint is supported by existing modeling. |
| 84 | + * |
| 85 | + * The check only takes Models as Data extension models into account. |
| 86 | + */ |
| 87 | + abstract boolean getSupportedStatus(); |
| 88 | + |
| 89 | + /** |
| 90 | + * Gets a string that describes the type of support detected this endpoint. |
| 91 | + * |
| 92 | + * The string can be one of the following: |
| 93 | + * - "source" if this endpoint is a known source. |
| 94 | + * - "sink" if this endpoint is a known sink. |
| 95 | + * - "summary" if this endpoint has a flow summary. |
| 96 | + * - "neutral" if this endpoint is a known neutral. |
| 97 | + * - "" if this endpoint is not detected as supported. |
| 98 | + */ |
| 99 | + abstract string getSupportedType(); |
| 100 | + |
| 101 | + /** Gets the kind of this endpoint. See `EndPointKind`. */ |
| 102 | + abstract EndpointKind getKind(); |
| 103 | +} |
| 104 | + |
| 105 | +private predicate sourceModelPath(string type, string path) { sourceModel(type, path, _, _) } |
| 106 | + |
| 107 | +module FindSourceModel = Util::FindModel<sourceModelPath/2>; |
| 108 | + |
| 109 | +private predicate sinkModelPath(string type, string path) { sinkModel(type, path, _, _) } |
| 110 | + |
| 111 | +module FindSinkModel = Util::FindModel<sinkModelPath/2>; |
| 112 | + |
| 113 | +private predicate summaryModelPath(string type, string path) { |
| 114 | + summaryModel(type, path, _, _, _, _) |
| 115 | +} |
| 116 | + |
| 117 | +module FindSummaryModel = Util::FindModel<summaryModelPath/2>; |
| 118 | + |
| 119 | +private predicate neutralModelPath(string type, string path) { neutralModel(type, path, _) } |
| 120 | + |
| 121 | +module FindNeutralModel = Util::FindModel<neutralModelPath/2>; |
| 122 | + |
| 123 | +/** |
| 124 | + * A callable function or method from source code. |
| 125 | + */ |
| 126 | +class FunctionEndpoint extends Endpoint instanceof Function { |
| 127 | + /** |
| 128 | + * Gets the parameter types of this endpoint. |
| 129 | + */ |
| 130 | + override string getParameters() { |
| 131 | + // For now, return the names of positional and keyword parameters. We don't always have type information, so we can't return type names. |
| 132 | + // We don't yet handle splat params or dict splat params. |
| 133 | + // |
| 134 | + // In Python, there are three types of parameters: |
| 135 | + // 1. Positional-only parameters: These are parameters that can only be passed by position and not by keyword. |
| 136 | + // 2. Positional-or-keyword parameters: These are parameters that can be passed by position or by keyword. |
| 137 | + // 3. Keyword-only parameters: These are parameters that can only be passed by keyword. |
| 138 | + // |
| 139 | + // The syntax for defining these parameters is as follows: |
| 140 | + // ```python |
| 141 | + // def f(a, /, b, *, c): |
| 142 | + // pass |
| 143 | + // ``` |
| 144 | + // In this example, `a` is a positional-only parameter, `b` is a positional-or-keyword parameter, and `c` is a keyword-only parameter. |
| 145 | + // |
| 146 | + // We handle positional-only parameters by adding a "/" to the parameter name, reminiscient of the syntax above. |
| 147 | + // Note that we don't yet have information about positional-only parameters. |
| 148 | + // We handle keyword-only parameters by adding a ":" to the parameter name, to be consistent with the MaD syntax and the other languages. |
| 149 | + exists(int nrPosOnly, Function f | |
| 150 | + f = this and |
| 151 | + nrPosOnly = f.getPositionalParameterCount() |
| 152 | + | |
| 153 | + result = |
| 154 | + "(" + |
| 155 | + concat(string key, string value | |
| 156 | + // TODO: Once we have information about positional-only parameters: |
| 157 | + // Handle positional-only parameters by adding a "/" |
| 158 | + value = any(int i | i.toString() = key | f.getArgName(i)) |
| 159 | + or |
| 160 | + exists(Name param | param = f.getAKeywordOnlyArg() | |
| 161 | + param.getId() = key and |
| 162 | + value = key + ":" |
| 163 | + ) |
| 164 | + | |
| 165 | + value, "," order by key |
| 166 | + ) + ")" |
| 167 | + ) |
| 168 | + } |
| 169 | + |
| 170 | + /** Holds if this API has a supported summary. */ |
| 171 | + pragma[nomagic] |
| 172 | + predicate hasSummary() { FindSummaryModel::hasModel(this) } |
| 173 | + |
| 174 | + /** Holds if this API is a known source. */ |
| 175 | + pragma[nomagic] |
| 176 | + predicate isSource() { FindSourceModel::hasModel(this) } |
| 177 | + |
| 178 | + /** Holds if this API is a known sink. */ |
| 179 | + pragma[nomagic] |
| 180 | + predicate isSink() { FindSinkModel::hasModel(this) } |
| 181 | + |
| 182 | + /** Holds if this API is a known neutral. */ |
| 183 | + pragma[nomagic] |
| 184 | + predicate isNeutral() { FindNeutralModel::hasModel(this) } |
| 185 | + |
| 186 | + /** |
| 187 | + * Holds if this API is supported by existing CodeQL libraries, that is, it is either a |
| 188 | + * recognized source, sink or neutral or it has a flow summary. |
| 189 | + */ |
| 190 | + predicate isSupported() { |
| 191 | + this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral() |
| 192 | + } |
| 193 | + |
| 194 | + override boolean getSupportedStatus() { |
| 195 | + if this.isSupported() then result = true else result = false |
| 196 | + } |
| 197 | + |
| 198 | + override string getSupportedType() { |
| 199 | + this.isSink() and result = "sink" |
| 200 | + or |
| 201 | + this.isSource() and result = "source" |
| 202 | + or |
| 203 | + this.hasSummary() and result = "summary" |
| 204 | + or |
| 205 | + this.isNeutral() and result = "neutral" |
| 206 | + or |
| 207 | + not this.isSupported() and result = "" |
| 208 | + } |
| 209 | + |
| 210 | + override EndpointKind getKind() { |
| 211 | + if this.(Function).isMethod() |
| 212 | + then |
| 213 | + result = this.methodKind() |
| 214 | + or |
| 215 | + not exists(this.methodKind()) and result = "InstanceMethod" |
| 216 | + else result = "Function" |
| 217 | + } |
| 218 | + |
| 219 | + private EndpointKind methodKind() { |
| 220 | + this.(Function).isMethod() and |
| 221 | + ( |
| 222 | + DP::isClassmethod(this) and result = "ClassMethod" |
| 223 | + or |
| 224 | + DP::isStaticmethod(this) and result = "StaticMethod" |
| 225 | + or |
| 226 | + this.(Function).isInitMethod() and result = "InitMethod" |
| 227 | + ) |
| 228 | + } |
| 229 | +} |
| 230 | + |
| 231 | +/** |
| 232 | + * A class from source code. |
| 233 | + */ |
| 234 | +class ClassEndpoint extends Endpoint instanceof Class { |
| 235 | + override string getClass() { result = type + "." + name } |
| 236 | + |
| 237 | + override string getFunctionName() { result = "" } |
| 238 | + |
| 239 | + override string getParameters() { result = "" } |
| 240 | + |
| 241 | + override boolean getSupportedStatus() { result = false } |
| 242 | + |
| 243 | + override string getSupportedType() { result = "" } |
| 244 | + |
| 245 | + override EndpointKind getKind() { result = "Class" } |
| 246 | +} |
0 commit comments