diff --git a/.github/workflows/go-tests.yml b/.github/workflows/go-tests.yml index 12e162adf29..ca126d1a3ee 100644 --- a/.github/workflows/go-tests.yml +++ b/.github/workflows/go-tests.yml @@ -4,6 +4,7 @@ on: paths: - "go/**" - .github/workflows/go-tests.yml + - codeql-workspace.yml jobs: test-linux: diff --git a/.github/workflows/js-ml-tests.yml b/.github/workflows/js-ml-tests.yml index ba33779455e..65db215d8c3 100644 --- a/.github/workflows/js-ml-tests.yml +++ b/.github/workflows/js-ml-tests.yml @@ -5,6 +5,7 @@ on: paths: - "javascript/ql/experimental/adaptivethreatmodeling/**" - .github/workflows/js-ml-tests.yml + - codeql-workspace.yml branches: - main - "rc/*" @@ -12,6 +13,7 @@ on: paths: - "javascript/ql/experimental/adaptivethreatmodeling/**" - .github/workflows/js-ml-tests.yml + - codeql-workspace.yml workflow_dispatch: defaults: diff --git a/.github/workflows/ql-for-ql-tests.yml b/.github/workflows/ql-for-ql-tests.yml index a6c79237759..3b0a4963b79 100644 --- a/.github/workflows/ql-for-ql-tests.yml +++ b/.github/workflows/ql-for-ql-tests.yml @@ -5,10 +5,12 @@ on: branches: [main] paths: - "ql/**" + - codeql-workspace.yml pull_request: branches: [main] paths: - "ql/**" + - codeql-workspace.yml env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/ruby-build.yml b/.github/workflows/ruby-build.yml index 9e95839789f..c402312db0e 100644 --- a/.github/workflows/ruby-build.yml +++ b/.github/workflows/ruby-build.yml @@ -5,6 +5,7 @@ on: paths: - "ruby/**" - .github/workflows/ruby-build.yml + - codeql-workspace.yml branches: - main - "rc/*" @@ -12,6 +13,7 @@ on: paths: - "ruby/**" - .github/workflows/ruby-build.yml + - codeql-workspace.yml branches: - main - "rc/*" diff --git a/.github/workflows/ruby-qltest.yml b/.github/workflows/ruby-qltest.yml index 463c501c765..0cf8860d8f1 100644 --- a/.github/workflows/ruby-qltest.yml +++ b/.github/workflows/ruby-qltest.yml @@ -5,6 +5,7 @@ on: paths: - "ruby/**" - .github/workflows/ruby-qltest.yml + - codeql-workspace.yml branches: - main - "rc/*" @@ -12,6 +13,7 @@ on: paths: - "ruby/**" - .github/workflows/ruby-qltest.yml + - codeql-workspace.yml branches: - main - "rc/*" diff --git a/.github/workflows/swift-qltest.yml b/.github/workflows/swift-qltest.yml index e0cc5a1cd02..915e1f331a5 100644 --- a/.github/workflows/swift-qltest.yml +++ b/.github/workflows/swift-qltest.yml @@ -5,6 +5,7 @@ on: paths: - "swift/**" - .github/workflows/swift-qltest.yml + - codeql-workspace.yml branches: - main defaults: diff --git a/config/identical-files.json b/config/identical-files.json index d6af2fc7e1d..192144ebb4f 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -525,7 +525,8 @@ "csharp/ql/lib/semmle/code/csharp/dataflow/internal/AccessPathSyntax.qll", "java/ql/lib/semmle/code/java/dataflow/internal/AccessPathSyntax.qll", "javascript/ql/lib/semmle/javascript/frameworks/data/internal/AccessPathSyntax.qll", - "ruby/ql/lib/codeql/ruby/dataflow/internal/AccessPathSyntax.qll" + "ruby/ql/lib/codeql/ruby/dataflow/internal/AccessPathSyntax.qll", + "python/ql/lib/semmle/python/frameworks/data/internal/AccessPathSyntax.qll" ], "IncompleteUrlSubstringSanitization": [ "javascript/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.qll", @@ -543,7 +544,8 @@ ], "ApiGraphModels": [ "javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll", - "ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll" + "ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll", + "python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll" ], "TaintedFormatStringQuery Ruby/JS": [ "javascript/ql/lib/semmle/javascript/security/dataflow/TaintedFormatStringQuery.qll", diff --git a/csharp/documentation/library-coverage/coverage.csv b/csharp/documentation/library-coverage/coverage.csv index 4d9996c1446..2bc6ca863ac 100644 --- a/csharp/documentation/library-coverage/coverage.csv +++ b/csharp/documentation/library-coverage/coverage.csv @@ -1,10 +1,27 @@ package,sink,source,summary,sink:code,sink:html,sink:remote,sink:sql,sink:xss,source:local,summary:taint,summary:value Dapper,55,,,,,,55,,,, +JsonToItemsTaskFactory,,,7,,,,,,,7, Microsoft.ApplicationBlocks.Data,28,,,,,,28,,,, +Microsoft.CSharp,,,24,,,,,,,24, Microsoft.EntityFrameworkCore,6,,,,,,6,,,, -Microsoft.Extensions.Primitives,,,54,,,,,,,54, -Microsoft.VisualBasic,,,4,,,,,,,,4 +Microsoft.Extensions.Caching.Distributed,,,15,,,,,,,15, +Microsoft.Extensions.Caching.Memory,,,46,,,,,,,45,1 +Microsoft.Extensions.Configuration,,,83,,,,,,,80,3 +Microsoft.Extensions.DependencyInjection,,,62,,,,,,,62, +Microsoft.Extensions.DependencyModel,,,12,,,,,,,12, +Microsoft.Extensions.FileProviders,,,15,,,,,,,15, +Microsoft.Extensions.FileSystemGlobbing,,,15,,,,,,,13,2 +Microsoft.Extensions.Hosting,,,17,,,,,,,16,1 +Microsoft.Extensions.Http,,,10,,,,,,,10, +Microsoft.Extensions.Logging,,,37,,,,,,,37, +Microsoft.Extensions.Options,,,8,,,,,,,8, +Microsoft.Extensions.Primitives,,,63,,,,,,,63, +Microsoft.Interop,,,27,,,,,,,27, +Microsoft.NET.Build.Tasks,,,1,,,,,,,1, +Microsoft.NETCore.Platforms.BuildTasks,,,4,,,,,,,4, +Microsoft.VisualBasic,,,9,,,,,,,5,4 +Microsoft.Win32,,,8,,,,,,,8, MySql.Data.MySqlClient,48,,,,,,48,,,, Newtonsoft.Json,,,91,,,,,,,73,18 ServiceStack,194,,7,27,,75,92,,,7, -System,28,3,2336,,4,,23,1,3,611,1725 +System,28,3,12038,,4,,23,1,3,10096,1942 diff --git a/csharp/documentation/library-coverage/coverage.rst b/csharp/documentation/library-coverage/coverage.rst index 9863b503fbf..076d2078d4b 100644 --- a/csharp/documentation/library-coverage/coverage.rst +++ b/csharp/documentation/library-coverage/coverage.rst @@ -8,7 +8,7 @@ C# framework & library support Framework / library,Package,Flow sources,Taint & value steps,Sinks (total),`CWE-079` :sub:`Cross-site scripting` `ServiceStack `_,"``ServiceStack.*``, ``ServiceStack``",,7,194, - System,"``System.*``, ``System``",3,2336,28,5 - Others,"``Dapper``, ``Microsoft.ApplicationBlocks.Data``, ``Microsoft.EntityFrameworkCore``, ``Microsoft.Extensions.Primitives``, ``Microsoft.VisualBasic``, ``MySql.Data.MySqlClient``, ``Newtonsoft.Json``",,149,137, - Totals,,3,2492,359,5 + System,"``System.*``, ``System``",3,12038,28,5 + Others,"``Dapper``, ``JsonToItemsTaskFactory``, ``Microsoft.ApplicationBlocks.Data``, ``Microsoft.CSharp``, ``Microsoft.EntityFrameworkCore``, ``Microsoft.Extensions.Caching.Distributed``, ``Microsoft.Extensions.Caching.Memory``, ``Microsoft.Extensions.Configuration``, ``Microsoft.Extensions.DependencyInjection``, ``Microsoft.Extensions.DependencyModel``, ``Microsoft.Extensions.FileProviders``, ``Microsoft.Extensions.FileSystemGlobbing``, ``Microsoft.Extensions.Hosting``, ``Microsoft.Extensions.Http``, ``Microsoft.Extensions.Logging``, ``Microsoft.Extensions.Options``, ``Microsoft.Extensions.Primitives``, ``Microsoft.Interop``, ``Microsoft.NET.Build.Tasks``, ``Microsoft.NETCore.Platforms.BuildTasks``, ``Microsoft.VisualBasic``, ``Microsoft.Win32``, ``MySql.Data.MySqlClient``, ``Newtonsoft.Json``",,554,137, + Totals,,3,12599,359,5 diff --git a/docs/codeql/support/reusables/versions-compilers.rst b/docs/codeql/support/reusables/versions-compilers.rst index a5f68cb64e1..4dfadcfb276 100644 --- a/docs/codeql/support/reusables/versions-compilers.rst +++ b/docs/codeql/support/reusables/versions-compilers.rst @@ -20,10 +20,10 @@ Java,"Java 7 to 18 [4]_","javac (OpenJDK and Oracle JDK), Eclipse compiler for Java (ECJ) [5]_",``.java`` - JavaScript,ECMAScript 2021 or lower,Not applicable,"``.js``, ``.jsx``, ``.mjs``, ``.es``, ``.es6``, ``.htm``, ``.html``, ``.xhtm``, ``.xhtml``, ``.vue``, ``.hbs``, ``.ejs``, ``.njk``, ``.json``, ``.yaml``, ``.yml``, ``.raml``, ``.xml`` [6]_" + JavaScript,ECMAScript 2022 or lower,Not applicable,"``.js``, ``.jsx``, ``.mjs``, ``.es``, ``.es6``, ``.htm``, ``.html``, ``.xhtm``, ``.xhtml``, ``.vue``, ``.hbs``, ``.ejs``, ``.njk``, ``.json``, ``.yaml``, ``.yml``, ``.raml``, ``.xml`` [6]_" Python,"2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10",Not applicable,``.py`` Ruby [7]_,"up to 3.0.2",Not applicable,"``.rb``, ``.erb``, ``.gemspec``, ``Gemfile``" - TypeScript [8]_,"2.6-4.6",Standard TypeScript compiler,"``.ts``, ``.tsx``" + TypeScript [8]_,"2.6-4.7",Standard TypeScript compiler,"``.ts``, ``.tsx``, ``.mts``, ``.cts``" .. container:: footnote-group diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index d381704e28f..c47e14cb0fd 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -491,11 +491,17 @@ open class KotlinFileExtractor( private fun extractValueParameter(vp: IrValueParameter, parent: Label, idx: Int, typeSubstitution: TypeSubstitution?, parentSourceDeclaration: Label, classTypeArgsIncludingOuterClasses: List?, extractTypeAccess: Boolean, locOverride: Label? = null): TypeResults { with("value parameter", vp) { val location = locOverride ?: getLocation(vp, classTypeArgsIncludingOuterClasses) + val maybeErasedType = (vp.parent as? IrFunction)?.let { + if (overridesCollectionsMethodWithAlteredParameterTypes(it)) + eraseCollectionsMethodParameterType(vp.type, it.name.asString(), idx) + else + null + } ?: vp.type val id = useValueParameter(vp, parent) if (extractTypeAccess) { - extractTypeAccessRecursive(typeSubstitution?.let { it(vp.type, TypeContext.OTHER, pluginContext) } ?: vp.type, location, id, -1) + extractTypeAccessRecursive(typeSubstitution?.let { it(maybeErasedType, TypeContext.OTHER, pluginContext) } ?: maybeErasedType, location, id, -1) } - return extractValueParameter(id, vp.type, vp.name.asString(), location, parent, idx, typeSubstitution, useValueParameter(vp, parentSourceDeclaration), vp.isVararg) + return extractValueParameter(id, maybeErasedType, vp.name.asString(), location, parent, idx, typeSubstitution, useValueParameter(vp, parentSourceDeclaration), vp.isVararg) } } @@ -524,7 +530,8 @@ open class KotlinFileExtractor( pluginContext.irBuiltIns.unitType, extensionReceiverParameter = null, functionTypeParameters = listOf(), - classTypeArgsIncludingOuterClasses = listOf() + classTypeArgsIncludingOuterClasses = listOf(), + overridesCollectionsMethod = false ) val clinitId = tw.getLabelFor(clinitLabel) val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN) @@ -759,7 +766,8 @@ open class KotlinFileExtractor( with("field", f) { DeclarationStackAdjuster(f).use { declarationStack.push(f) - return extractField(useField(f), f.name.asString(), f.type, parentId, tw.getLocation(f), f.visibility, f, isExternalDeclaration(f), f.isFinal) + val fNameSuffix = getExtensionReceiverType(f)?.let { it.classFqName?.asString()?.replace(".", "$$") } ?: "" + return extractField(useField(f), "${f.name.asString()}$fNameSuffix", f.type, parentId, tw.getLocation(f), f.visibility, f, isExternalDeclaration(f), f.isFinal) } } } @@ -1394,12 +1402,6 @@ open class KotlinFileExtractor( result } - val javaLangObject by lazy { - val result = pluginContext.referenceClass(FqName("java.lang.Object"))?.owner - result?.let { extractExternalClassLater(it) } - result - } - val objectCloneMethod by lazy { val result = javaLangObject?.declarations?.find { it is IrFunction && it.name.asString() == "clone" diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt index 8bbcb2ee2dd..42ecbbff8cc 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt @@ -10,12 +10,14 @@ import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.* import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name @@ -32,6 +34,17 @@ open class KotlinUsesExtractor( val pluginContext: IrPluginContext, val globalExtensionState: KotlinExtractorGlobalState ) { + + val javaLangObject by lazy { + val result = pluginContext.referenceClass(FqName("java.lang.Object"))?.owner + result?.let { extractExternalClassLater(it) } + result + } + + val javaLangObjectType by lazy { + javaLangObject?.typeWith() + } + fun usePackage(pkg: String): Label { return extractPackage(pkg) } @@ -790,6 +803,52 @@ open class KotlinUsesExtractor( else -> null } + val javaUtilCollection by lazy { + val result = pluginContext.referenceClass(FqName("java.util.Collection"))?.owner + result?.let { extractExternalClassLater(it) } + result + } + + val wildcardCollectionType by lazy { + javaUtilCollection?.let { + it.symbol.typeWithArguments(listOf(IrStarProjectionImpl)) + } + } + + private fun makeCovariant(t: IrTypeArgument) = + t.typeOrNull?.let { makeTypeProjection(it, Variance.OUT_VARIANCE) } ?: t + + private fun makeArgumentsCovariant(t: IrType) = (t as? IrSimpleType)?.let { + t.toBuilder().also { b -> b.arguments = b.arguments.map(this::makeCovariant) }.buildSimpleType() + } ?: t + + fun eraseCollectionsMethodParameterType(t: IrType, collectionsMethodName: String, paramIdx: Int) = + when(collectionsMethodName) { + "contains", "remove", "containsKey", "containsValue", "get", "indexOf", "lastIndexOf" -> javaLangObjectType + "getOrDefault" -> if (paramIdx == 0) javaLangObjectType else null + "containsAll", "removeAll", "retainAll" -> wildcardCollectionType + // Kotlin defines these like addAll(Collection); Java uses addAll(Collection) + "putAll", "addAll" -> makeArgumentsCovariant(t) + else -> null + } ?: t + + private fun overridesFunctionDefinedOn(f: IrFunction, packageName: String, className: String) = + (f as? IrSimpleFunction)?.let { + it.overriddenSymbols.any { overridden -> + overridden.owner.parentClassOrNull?.let { defnClass -> + defnClass.name.asString() == className && + defnClass.packageFqName?.asString() == packageName + } ?: false + } + } ?: false + + @OptIn(ObsoleteDescriptorBasedAPI::class) + fun overridesCollectionsMethodWithAlteredParameterTypes(f: IrFunction) = + BuiltinMethodsWithSpecialGenericSignature.getOverriddenBuiltinFunctionWithErasedValueParametersInJava(f.descriptor) != null || + (f.name.asString() == "putAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableMap")) || + (f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableCollection")) || + (f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList")) + /* * This is the normal getFunctionLabel function to use. If you want * to refer to the function in its source class then @@ -811,8 +870,19 @@ open class KotlinUsesExtractor( * `java.lang.Throwable`, which isn't what we want. So we have to * allow it to be passed in. */ + @OptIn(ObsoleteDescriptorBasedAPI::class) fun getFunctionLabel(f: IrFunction, maybeParentId: Label?, classTypeArgsIncludingOuterClasses: List?) = - getFunctionLabel(f.parent, maybeParentId, getFunctionShortName(f).nameInDB, f.valueParameters, f.returnType, f.extensionReceiverParameter, getFunctionTypeParameters(f), classTypeArgsIncludingOuterClasses) + getFunctionLabel( + f.parent, + maybeParentId, + getFunctionShortName(f).nameInDB, + f.valueParameters, + f.returnType, + f.extensionReceiverParameter, + getFunctionTypeParameters(f), + classTypeArgsIncludingOuterClasses, + overridesCollectionsMethodWithAlteredParameterTypes(f) + ) /* * This function actually generates the label for a function. @@ -838,6 +908,9 @@ open class KotlinUsesExtractor( functionTypeParameters: List, // The type arguments of enclosing classes of the function. classTypeArgsIncludingOuterClasses: List?, + // If true, this method implements a Java Collections interface (Collection, Map or List) and may need + // parameter erasure to match the way this class will appear to an external consumer of the .class file. + overridesCollectionsMethod: Boolean, // The prefix used in the label. "callable", unless a property label is created, then it's "property". prefix: String = "callable" ): String { @@ -856,14 +929,19 @@ open class KotlinUsesExtractor( enclosingClass?.let { notNullClass -> makeTypeGenericSubstitutionMap(notNullClass, notNullArgs) } } } - val getIdForFunctionLabel = { it: IrValueParameter -> - // Mimic the Java extractor's behaviour: functions with type parameters are named for their erased types; + val getIdForFunctionLabel = { it: IndexedValue -> + // Kotlin rewrites certain Java collections types adding additional generic constraints-- for example, + // Collection.remove(Object) because Collection.remove(Collection::E) in the Kotlin universe. + // If this has happened, erase the type again to get the correct Java signature. + val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value.type, name, it.index) else it.value.type + // Now substitute any class type parameters in: + val maybeSubbed = maybeAmendedForCollections.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext) + // Finally, mimic the Java extractor's behaviour by naming functions with type parameters for their erased types; // those without type parameters are named for the generic type. - val maybeSubbed = it.type.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext) val maybeErased = if (functionTypeParameters.isEmpty()) maybeSubbed else erase(maybeSubbed) "{${useType(maybeErased).javaResult.id}}" } - val paramTypeIds = allParams.joinToString(separator = ",", transform = getIdForFunctionLabel) + val paramTypeIds = allParams.withIndex().joinToString(separator = ",", transform = getIdForFunctionLabel) val labelReturnType = if (name == "") pluginContext.irBuiltIns.unitType @@ -1269,6 +1347,7 @@ open class KotlinUsesExtractor( fun useValueParameter(vp: IrValueParameter, parent: Label?): Label = tw.getLabelFor(getValueParameterLabel(vp, parent)) + fun isDirectlyExposedCompanionObjectField(f: IrField) = f.hasAnnotation(FqName("kotlin.jvm.JvmField")) || f.correspondingPropertySymbol?.owner?.let { @@ -1283,9 +1362,20 @@ open class KotlinUsesExtractor( null } ?: f.parent + // Gets a field's corresponding property's extension receiver type, if any + fun getExtensionReceiverType(f: IrField) = + f.correspondingPropertySymbol?.owner?.let { + (it.getter ?: it.setter)?.extensionReceiverParameter?.type + } + fun getFieldLabel(f: IrField): String { val parentId = useDeclarationParent(getFieldParent(f), false) - return "@\"field;{$parentId};${f.name.asString()}\"" + // Distinguish backing fields of properties based on their extension receiver type; + // otherwise two extension properties declared in the same enclosing context will get + // clashing trap labels. These are always private, so we can just make up a label without + // worrying about their names as seen from Java. + val extensionPropertyDiscriminator = getExtensionReceiverType(f)?.let { "extension;${useType(it)}" } ?: "" + return "@\"field;{$parentId};${extensionPropertyDiscriminator}${f.name.asString()}\"" } fun useField(f: IrField): Label = @@ -1313,7 +1403,7 @@ open class KotlinUsesExtractor( val returnType = getter?.returnType ?: setter?.valueParameters?.singleOrNull()?.type ?: pluginContext.irBuiltIns.unitType val typeParams = getFunctionTypeParameters(func) - getFunctionLabel(p.parent, parentId, p.name.asString(), listOf(), returnType, ext, typeParams, classTypeArgsIncludingOuterClasses, "property") + getFunctionLabel(p.parent, parentId, p.name.asString(), listOf(), returnType, ext, typeParams, classTypeArgsIncludingOuterClasses, overridesCollectionsMethod = false, prefix = "property") } } diff --git a/java/ql/src/utils/model-generator/CaptureSinkModels.ql b/java/ql/src/utils/model-generator/CaptureSinkModels.ql index 8fd2e4d2a30..fb04c4ede00 100644 --- a/java/ql/src/utils/model-generator/CaptureSinkModels.ql +++ b/java/ql/src/utils/model-generator/CaptureSinkModels.ql @@ -1,7 +1,9 @@ /** * @name Capture sink models. * @description Finds public methods that act as sinks as they flow into a a known sink. + * @kind diagnostic * @id java/utils/model-generator/sink-models + * @tags model-generator */ private import internal.CaptureModels diff --git a/java/ql/src/utils/model-generator/CaptureSourceModels.ql b/java/ql/src/utils/model-generator/CaptureSourceModels.ql index 49e9cb34990..d37cf5e78fb 100644 --- a/java/ql/src/utils/model-generator/CaptureSourceModels.ql +++ b/java/ql/src/utils/model-generator/CaptureSourceModels.ql @@ -1,7 +1,9 @@ /** * @name Capture source models. * @description Finds APIs that act as sources as they expose already known sources. - * @id java/utils/model-generator/sink-models + * @kind diagnostic + * @id java/utils/model-generator/source-models + * @tags model-generator */ private import internal.CaptureModels diff --git a/java/ql/src/utils/model-generator/CaptureSummaryModels.ql b/java/ql/src/utils/model-generator/CaptureSummaryModels.ql index 8f4eab20722..253c3d4ed46 100644 --- a/java/ql/src/utils/model-generator/CaptureSummaryModels.ql +++ b/java/ql/src/utils/model-generator/CaptureSummaryModels.ql @@ -1,7 +1,9 @@ /** * @name Capture summary models. * @description Finds applicable summary models to be used by other queries. + * @kind diagnostic * @id java/utils/model-generator/summary-models + * @tags model-generator */ private import internal.CaptureModels diff --git a/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.expected b/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.expected new file mode 100644 index 00000000000..72fe7124394 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.expected @@ -0,0 +1,9 @@ +| | +| | +| A | +| B | +| get | +| getX | +| invoke | +| x$delegatepackagename$$subpackagename$$A | +| x$delegatepackagename$$subpackagename$$B | diff --git a/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.kt b/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.kt new file mode 100644 index 00000000000..9a7c5d51801 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.kt @@ -0,0 +1,7 @@ +package packagename.subpackagename + +public class A { } +public class B { } + +val A.x : String by lazy { "HelloA" } +val B.x : String by lazy { "HelloB" } diff --git a/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.ql b/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.ql new file mode 100644 index 00000000000..4c0a8d53b1f --- /dev/null +++ b/java/ql/test/kotlin/library-tests/clashing-extension-fields/test.ql @@ -0,0 +1,5 @@ +import java + +from Class c +where c.fromSource() +select c.getAMember().toString() diff --git a/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected b/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected index 4a93ee5df3b..4fdd6b4d718 100644 --- a/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected +++ b/java/ql/test/kotlin/library-tests/exprs/PrintAst.expected @@ -31,7 +31,7 @@ delegatedProperties.kt: # 87| 0: [MethodAccess] getValue(...) # 87| -2: [TypeAccess] Integer # 87| -1: [TypeAccess] PropertyReferenceDelegatesKt -# 87| 0: [VarAccess] DelegatedPropertiesKt.extDelegated$delegate +# 87| 0: [VarAccess] DelegatedPropertiesKt.extDelegated$delegateMyClass # 87| -1: [TypeAccess] DelegatedPropertiesKt # 1| 1: [ExtensionReceiverAccess] this # 87| 2: [PropertyRefExpr] ...::... @@ -80,7 +80,7 @@ delegatedProperties.kt: # 87| 0: [MethodAccess] setValue(...) # 87| -2: [TypeAccess] Integer # 87| -1: [TypeAccess] PropertyReferenceDelegatesKt -# 87| 0: [VarAccess] DelegatedPropertiesKt.extDelegated$delegate +# 87| 0: [VarAccess] DelegatedPropertiesKt.extDelegated$delegateMyClass # 87| -1: [TypeAccess] DelegatedPropertiesKt # 1| 1: [ExtensionReceiverAccess] this # 87| 2: [PropertyRefExpr] ...::... @@ -118,7 +118,7 @@ delegatedProperties.kt: # 87| 0: [TypeAccess] MyClass # 87| 1: [TypeAccess] Integer # 87| 3: [VarAccess] -# 87| 5: [FieldDeclaration] KMutableProperty0 extDelegated$delegate; +# 87| 5: [FieldDeclaration] KMutableProperty0 extDelegated$delegateMyClass; # 87| -1: [TypeAccess] KMutableProperty0 # 87| 0: [TypeAccess] Integer # 87| 0: [PropertyRefExpr] ...::... diff --git a/java/ql/test/kotlin/library-tests/exprs/delegatedProperties.expected b/java/ql/test/kotlin/library-tests/exprs/delegatedProperties.expected index c60b200a2eb..7141b6d1531 100644 --- a/java/ql/test/kotlin/library-tests/exprs/delegatedProperties.expected +++ b/java/ql/test/kotlin/library-tests/exprs/delegatedProperties.expected @@ -16,7 +16,7 @@ delegatedProperties | delegatedProperties.kt:77:5:77:49 | delegatedToTopLevel | delegatedToTopLevel | non-local | delegatedProperties.kt:77:34:77:49 | delegatedToTopLevel$delegate | delegatedProperties.kt:77:37:77:49 | ...::... | | delegatedProperties.kt:79:5:79:38 | max | max | non-local | delegatedProperties.kt:79:18:79:38 | max$delegate | delegatedProperties.kt:79:21:79:38 | ...::... | | delegatedProperties.kt:82:9:82:54 | delegatedToMember3 | delegatedToMember3 | local | delegatedProperties.kt:82:37:82:54 | KMutableProperty0 delegatedToMember3$delegate | delegatedProperties.kt:82:40:82:54 | ...::... | -| delegatedProperties.kt:87:1:87:46 | extDelegated | extDelegated | non-local | delegatedProperties.kt:87:31:87:46 | extDelegated$delegate | delegatedProperties.kt:87:34:87:46 | ...::... | +| delegatedProperties.kt:87:1:87:46 | extDelegated | extDelegated | non-local | delegatedProperties.kt:87:31:87:46 | extDelegated$delegateMyClass | delegatedProperties.kt:87:34:87:46 | ...::... | delegatedPropertyTypes | delegatedProperties.kt:6:9:9:9 | prop1 | file://:0:0:0:0 | int | file:///Lazy.class:0:0:0:0 | Lazy | | delegatedProperties.kt:19:9:19:51 | varResource1 | file://:0:0:0:0 | int | delegatedProperties.kt:45:1:51:1 | ResourceDelegate | diff --git a/java/ql/test/kotlin/library-tests/exprs/exprs.expected b/java/ql/test/kotlin/library-tests/exprs/exprs.expected index a2a1e3bd941..110d9e47147 100644 --- a/java/ql/test/kotlin/library-tests/exprs/exprs.expected +++ b/java/ql/test/kotlin/library-tests/exprs/exprs.expected @@ -830,9 +830,9 @@ | delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt | delegatedProperties.kt:87:31:87:46 | set | TypeAccess | | delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt | delegatedProperties.kt:87:31:87:46 | set | TypeAccess | | delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt | delegatedProperties.kt:87:31:87:46 | setExtDelegated | TypeAccess | -| delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt.extDelegated$delegate | delegatedProperties.kt:0:0:0:0 | | VarAccess | -| delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt.extDelegated$delegate | delegatedProperties.kt:87:31:87:46 | getExtDelegated | VarAccess | -| delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt.extDelegated$delegate | delegatedProperties.kt:87:31:87:46 | setExtDelegated | VarAccess | +| delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt.extDelegated$delegateMyClass | delegatedProperties.kt:0:0:0:0 | | VarAccess | +| delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt.extDelegated$delegateMyClass | delegatedProperties.kt:87:31:87:46 | getExtDelegated | VarAccess | +| delegatedProperties.kt:87:31:87:46 | DelegatedPropertiesKt.extDelegated$delegateMyClass | delegatedProperties.kt:87:31:87:46 | setExtDelegated | VarAccess | | delegatedProperties.kt:87:31:87:46 | Integer | delegatedProperties.kt:87:31:87:46 | getExtDelegated | TypeAccess | | delegatedProperties.kt:87:31:87:46 | Integer | delegatedProperties.kt:87:31:87:46 | setExtDelegated | TypeAccess | | delegatedProperties.kt:87:31:87:46 | Integer | file://:0:0:0:0 | | TypeAccess | diff --git a/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/Test.java b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/Test.java new file mode 100644 index 00000000000..12a77514c3d --- /dev/null +++ b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/Test.java @@ -0,0 +1,28 @@ +import java.util.Map; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.AbstractCollection; +import java.util.List; +import java.util.AbstractList; + +public class Test { + + public static void test( + Map p1, + AbstractMap p2, + Collection p3, + AbstractCollection p4, + List p5, + AbstractList p6) { + + // Use a method of each to ensure method prototypes are extracted: + p1.remove("Hello"); + p2.remove("Hello"); + p3.remove("Hello"); + p4.remove("Hello"); + p5.remove("Hello"); + p6.remove("Hello"); + + } + +} diff --git a/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.expected b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.expected new file mode 100644 index 00000000000..8b681d0f785 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.expected @@ -0,0 +1,412 @@ +methodWithDuplicate +#select +| AbstractCollection | add | E | +| AbstractCollection | addAll | Collection | +| AbstractCollection | contains | Object | +| AbstractCollection | containsAll | Collection | +| AbstractCollection | remove | Object | +| AbstractCollection | removeAll | Collection | +| AbstractCollection | retainAll | Collection | +| AbstractCollection | toArray | T[] | +| AbstractCollection | add | E | +| AbstractCollection | addAll | Collection | +| AbstractCollection | contains | Object | +| AbstractCollection | containsAll | Collection | +| AbstractCollection | remove | Object | +| AbstractCollection | removeAll | Collection | +| AbstractCollection | retainAll | Collection | +| AbstractCollection | toArray | T[] | +| AbstractCollection | add | String | +| AbstractCollection | addAll | Collection | +| AbstractCollection | contains | Object | +| AbstractCollection | containsAll | Collection | +| AbstractCollection | remove | Object | +| AbstractCollection | removeAll | Collection | +| AbstractCollection | retainAll | Collection | +| AbstractCollection | toArray | T[] | +| AbstractList | add | E | +| AbstractList | add | int | +| AbstractList | addAll | Collection | +| AbstractList | addAll | int | +| AbstractList | equals | Object | +| AbstractList | get | int | +| AbstractList | indexOf | Object | +| AbstractList | lastIndexOf | Object | +| AbstractList | listIterator | int | +| AbstractList | removeAt | int | +| AbstractList | removeRange | int | +| AbstractList | set | E | +| AbstractList | set | int | +| AbstractList | subList | int | +| AbstractList | subListRangeCheck | int | +| AbstractList | add | E | +| AbstractList | add | int | +| AbstractList | addAll | Collection | +| AbstractList | addAll | int | +| AbstractList | equals | Object | +| AbstractList | get | int | +| AbstractList | indexOf | Object | +| AbstractList | lastIndexOf | Object | +| AbstractList | listIterator | int | +| AbstractList | removeAt | int | +| AbstractList | removeRange | int | +| AbstractList | set | E | +| AbstractList | set | int | +| AbstractList | subList | int | +| AbstractList | subListRangeCheck | int | +| AbstractMap | containsEntry | Entry | +| AbstractMap | containsKey | Object | +| AbstractMap | containsValue | Object | +| AbstractMap | equals | Object | +| AbstractMap | get | Object | +| AbstractMap | put | K | +| AbstractMap | put | V | +| AbstractMap | putAll | Map | +| AbstractMap | remove | Object | +| AbstractMap> | containsKey | Object | +| AbstractMap> | containsValue | Object | +| AbstractMap> | equals | Object | +| AbstractMap> | get | Object | +| AbstractMap> | put | Entry | +| AbstractMap> | put | Identity | +| AbstractMap> | putAll | Map> | +| AbstractMap> | remove | Object | +| AbstractMap | containsKey | Object | +| AbstractMap | containsValue | Object | +| AbstractMap | equals | Object | +| AbstractMap | get | Object | +| AbstractMap | put | K | +| AbstractMap | put | V | +| AbstractMap | putAll | Map | +| AbstractMap | remove | Object | +| AbstractMap | containsEntry | Entry | +| AbstractMap | containsKey | Object | +| AbstractMap | containsValue | Object | +| AbstractMap | equals | Object | +| AbstractMap | get | Object | +| AbstractMap | put | String | +| AbstractMap | putAll | Map | +| AbstractMap | remove | Object | +| AbstractMutableCollection | add | E | +| AbstractMutableList | add | E | +| AbstractMutableList | add | int | +| AbstractMutableList | removeAt | int | +| AbstractMutableList | set | E | +| AbstractMutableList | set | int | +| AbstractMutableMap | put | K | +| AbstractMutableMap | put | V | +| Collection | add | E | +| Collection | addAll | Collection | +| Collection | contains | Object | +| Collection | containsAll | Collection | +| Collection | equals | Object | +| Collection | remove | Object | +| Collection | removeAll | Collection | +| Collection | removeIf | Predicate | +| Collection | retainAll | Collection | +| Collection | toArray | IntFunction | +| Collection | toArray | T[] | +| Collection | add | E | +| Collection | addAll | Collection | +| Collection | contains | Object | +| Collection | containsAll | Collection | +| Collection | remove | Object | +| Collection | removeAll | Collection | +| Collection | removeIf | Predicate | +| Collection | retainAll | Collection | +| Collection | toArray | IntFunction | +| Collection | toArray | T[] | +| Collection> | add | Entry | +| Collection> | addAll | Collection> | +| Collection> | contains | Object | +| Collection> | containsAll | Collection | +| Collection> | remove | Object | +| Collection> | removeAll | Collection | +| Collection> | removeIf | Predicate> | +| Collection> | retainAll | Collection | +| Collection> | toArray | IntFunction | +| Collection> | toArray | T[] | +| Collection | add | K | +| Collection | addAll | Collection | +| Collection | contains | Object | +| Collection | containsAll | Collection | +| Collection | remove | Object | +| Collection | removeAll | Collection | +| Collection | removeIf | Predicate | +| Collection | retainAll | Collection | +| Collection | toArray | IntFunction | +| Collection | toArray | T[] | +| Collection | add | String | +| Collection | addAll | Collection | +| Collection | contains | Object | +| Collection | containsAll | Collection | +| Collection | equals | Object | +| Collection | remove | Object | +| Collection | removeAll | Collection | +| Collection | removeIf | Predicate | +| Collection | retainAll | Collection | +| Collection | toArray | IntFunction | +| Collection | toArray | T[] | +| Collection | add | V | +| Collection | addAll | Collection | +| Collection | contains | Object | +| Collection | containsAll | Collection | +| Collection | remove | Object | +| Collection | removeAll | Collection | +| Collection | removeIf | Predicate | +| Collection | retainAll | Collection | +| Collection | toArray | IntFunction | +| Collection | toArray | T[] | +| List | add | E | +| List | add | int | +| List | addAll | Collection | +| List | addAll | int | +| List | contains | Object | +| List | containsAll | Collection | +| List | copyOf | Collection | +| List | equals | Object | +| List | get | int | +| List | indexOf | Object | +| List | lastIndexOf | Object | +| List | listIterator | int | +| List | of | E | +| List | of | E[] | +| List | remove | Object | +| List | remove | int | +| List | removeAll | Collection | +| List | replaceAll | UnaryOperator | +| List | retainAll | Collection | +| List | set | E | +| List | set | int | +| List | sort | Comparator | +| List | subList | int | +| List | toArray | T[] | +| List | add | E | +| List | add | int | +| List | addAll | Collection | +| List | addAll | int | +| List | contains | Object | +| List | containsAll | Collection | +| List | copyOf | Collection | +| List | get | int | +| List | indexOf | Object | +| List | lastIndexOf | Object | +| List | listIterator | int | +| List | of | E | +| List | of | E[] | +| List | remove | Object | +| List | remove | int | +| List | removeAll | Collection | +| List | replaceAll | UnaryOperator | +| List | retainAll | Collection | +| List | set | E | +| List | set | int | +| List | sort | Comparator | +| List | subList | int | +| List | toArray | T[] | +| List | add | String | +| List | add | int | +| List | addAll | Collection | +| List | addAll | int | +| List | contains | Object | +| List | containsAll | Collection | +| List | copyOf | Collection | +| List | equals | Object | +| List | get | int | +| List | indexOf | Object | +| List | lastIndexOf | Object | +| List | listIterator | int | +| List | of | E | +| List | of | E[] | +| List | remove | Object | +| List | remove | int | +| List | removeAll | Collection | +| List | replaceAll | UnaryOperator | +| List | retainAll | Collection | +| List | set | String | +| List | set | int | +| List | sort | Comparator | +| List | subList | int | +| List | toArray | T[] | +| Map | compute | BiFunction | +| Map | compute | K | +| Map | computeIfAbsent | Function | +| Map | computeIfAbsent | K | +| Map | computeIfPresent | BiFunction | +| Map | computeIfPresent | K | +| Map | containsKey | Object | +| Map | containsValue | Object | +| Map | copyOf | Map | +| Map | entry | K | +| Map | entry | V | +| Map | equals | Object | +| Map | forEach | BiConsumer | +| Map | get | Object | +| Map | getOrDefault | Object | +| Map | getOrDefault | V | +| Map | merge | BiFunction | +| Map | merge | K | +| Map | merge | V | +| Map | of | K | +| Map | of | V | +| Map | ofEntries | Entry[] | +| Map | put | K | +| Map | put | V | +| Map | putAll | Map | +| Map | putIfAbsent | K | +| Map | putIfAbsent | V | +| Map | remove | Object | +| Map | replace | K | +| Map | replace | V | +| Map | replaceAll | BiFunction | +| Map> | compute | BiFunction,? extends Entry> | +| Map> | compute | Identity | +| Map> | computeIfAbsent | Function> | +| Map> | computeIfAbsent | Identity | +| Map> | computeIfPresent | BiFunction,? extends Entry> | +| Map> | computeIfPresent | Identity | +| Map> | containsKey | Object | +| Map> | containsValue | Object | +| Map> | copyOf | Map | +| Map> | entry | K | +| Map> | entry | V | +| Map> | forEach | BiConsumer> | +| Map> | get | Object | +| Map> | getOrDefault | Entry | +| Map> | getOrDefault | Object | +| Map> | merge | BiFunction,? super Entry,? extends Entry> | +| Map> | merge | Entry | +| Map> | merge | Identity | +| Map> | of | K | +| Map> | of | V | +| Map> | ofEntries | Entry[] | +| Map> | put | Entry | +| Map> | put | Identity | +| Map> | putAll | Map> | +| Map> | putIfAbsent | Entry | +| Map> | putIfAbsent | Identity | +| Map> | remove | Object | +| Map> | replace | Entry | +| Map> | replace | Identity | +| Map> | replaceAll | BiFunction,? extends Entry> | +| Map | compute | BiFunction | +| Map | compute | K | +| Map | computeIfAbsent | Function | +| Map | computeIfAbsent | K | +| Map | computeIfPresent | BiFunction | +| Map | computeIfPresent | K | +| Map | containsKey | Object | +| Map | containsValue | Object | +| Map | copyOf | Map | +| Map | entry | K | +| Map | entry | V | +| Map | forEach | BiConsumer | +| Map | get | Object | +| Map | getOrDefault | Object | +| Map | getOrDefault | V | +| Map | merge | BiFunction | +| Map | merge | K | +| Map | merge | V | +| Map | of | K | +| Map | of | V | +| Map | ofEntries | Entry[] | +| Map | put | K | +| Map | put | V | +| Map | putAll | Map | +| Map | putIfAbsent | K | +| Map | putIfAbsent | V | +| Map | remove | Object | +| Map | replace | K | +| Map | replace | V | +| Map | replaceAll | BiFunction | +| Map | compute | BiFunction | +| Map | compute | Object | +| Map | computeIfAbsent | Function | +| Map | computeIfAbsent | Object | +| Map | computeIfPresent | BiFunction | +| Map | computeIfPresent | Object | +| Map | containsKey | Object | +| Map | containsValue | Object | +| Map | copyOf | Map | +| Map | entry | K | +| Map | entry | V | +| Map | forEach | BiConsumer | +| Map | get | Object | +| Map | getOrDefault | Object | +| Map | merge | BiFunction | +| Map | merge | Object | +| Map | of | K | +| Map | of | V | +| Map | ofEntries | Entry[] | +| Map | put | Object | +| Map | putAll | Map | +| Map | putIfAbsent | Object | +| Map | remove | Object | +| Map | replace | Object | +| Map | replaceAll | BiFunction | +| Map | compute | BiFunction | +| Map | compute | String | +| Map | computeIfAbsent | Function | +| Map | computeIfAbsent | String | +| Map | computeIfPresent | BiFunction | +| Map | computeIfPresent | String | +| Map | containsKey | Object | +| Map | containsValue | Object | +| Map | copyOf | Map | +| Map | entry | K | +| Map | entry | V | +| Map | equals | Object | +| Map | forEach | BiConsumer | +| Map | get | Object | +| Map | getOrDefault | Object | +| Map | getOrDefault | String | +| Map | merge | BiFunction | +| Map | merge | String | +| Map | of | K | +| Map | of | V | +| Map | ofEntries | Entry[] | +| Map | put | String | +| Map | putAll | Map | +| Map | putIfAbsent | String | +| Map | remove | Object | +| Map | replace | String | +| Map | replaceAll | BiFunction | +| MutableCollection | add | E | +| MutableCollection | addAll | Collection | +| MutableCollection | remove | Object | +| MutableCollection | removeAll | Collection | +| MutableCollection | removeIf | Predicate | +| MutableCollection | retainAll | Collection | +| MutableList | add | E | +| MutableList | add | int | +| MutableList | addAll | Collection | +| MutableList | addAll | Collection | +| MutableList | addAll | int | +| MutableList | listIterator | int | +| MutableList | remove | Object | +| MutableList | removeAll | Collection | +| MutableList | removeAt | int | +| MutableList | replaceAll | UnaryOperator | +| MutableList | retainAll | Collection | +| MutableList | set | E | +| MutableList | set | int | +| MutableList | sort | Comparator | +| MutableList | subList | int | +| MutableMap | compute | BiFunction | +| MutableMap | compute | K | +| MutableMap | computeIfAbsent | Function | +| MutableMap | computeIfAbsent | K | +| MutableMap | computeIfPresent | BiFunction | +| MutableMap | computeIfPresent | K | +| MutableMap | merge | BiFunction | +| MutableMap | merge | K | +| MutableMap | merge | V | +| MutableMap | put | K | +| MutableMap | put | V | +| MutableMap | putAll | Map | +| MutableMap | putIfAbsent | K | +| MutableMap | putIfAbsent | V | +| MutableMap | remove | Object | +| MutableMap | replace | K | +| MutableMap | replace | V | +| MutableMap | replaceAll | BiFunction | diff --git a/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.kt b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.kt new file mode 100644 index 00000000000..a597bbb8490 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.kt @@ -0,0 +1,29 @@ +fun test( + p1: Map, + p2: AbstractMap, + p3: Collection, + p4: AbstractCollection, + p5: List, + p6: AbstractList, + p7: MutableMap, + p8: AbstractMutableMap, + p9: MutableCollection, + p10: AbstractMutableCollection, + p11: MutableList, + p12: AbstractMutableList) { + + // Use a method of each to ensure method prototypes are extracted: + p1.get("Hello"); + p2.get("Hello"); + p3.contains("Hello"); + p4.contains("Hello"); + p5.contains("Hello"); + p6.contains("Hello"); + p7.remove("Hello"); + p8.remove("Hello"); + p9.remove("Hello"); + p10.remove("Hello"); + p11.remove("Hello"); + p12.remove("Hello"); + +} diff --git a/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.ql b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.ql new file mode 100644 index 00000000000..22a78af1dea --- /dev/null +++ b/java/ql/test/kotlin/library-tests/java-kotlin-collection-type-generic-methods/test.ql @@ -0,0 +1,28 @@ +import java + +RefType getARelevantCollectionType() { + result + .hasQualifiedName(["java.util", "kotlin.collections"], + ["Abstract", ""] + ["Mutable", ""] + ["Collection", "List", "Map"]) +} + +class RelevantMethod extends Method { + RelevantMethod() { this.getDeclaringType().getSourceDeclaration() = getARelevantCollectionType() } +} + +// Check for methods with suspicious twins -- probably another extraction of the same method outline which was given a different trap key. +// It so happens the collections methods of interest to this test don't use overloads with the same parameter count. +query predicate methodWithDuplicate(string methodName, string typeName) { + exists(RelevantMethod m, RelevantMethod dup | + dup.getName() = m.getName() and + not dup.getName() = ["of", "remove", "toArray"] and // These really do have overloads with the same parameter count, so it isn't trivial to tell if they are intentional overloads or inappropriate duplicates. + dup.getNumberOfParameters() = m.getNumberOfParameters() and + dup.getDeclaringType() = m.getDeclaringType() and + dup != m and + methodName = m.getName() and + typeName = m.getDeclaringType().getName() + ) +} + +from RelevantMethod m +select m.getDeclaringType().getName(), m.getName(), m.getAParamType().getName() diff --git a/javascript/extractor/lib/typescript/package.json b/javascript/extractor/lib/typescript/package.json index 3053bcef1bf..6898986e749 100644 --- a/javascript/extractor/lib/typescript/package.json +++ b/javascript/extractor/lib/typescript/package.json @@ -2,7 +2,7 @@ "name": "typescript-parser-wrapper", "private": true, "dependencies": { - "typescript": "4.6.2" + "typescript": "4.7.2" }, "scripts": { "build": "tsc --project tsconfig.json", diff --git a/javascript/extractor/lib/typescript/yarn.lock b/javascript/extractor/lib/typescript/yarn.lock index f7d0c40fed8..3a10c8452f0 100644 --- a/javascript/extractor/lib/typescript/yarn.lock +++ b/javascript/extractor/lib/typescript/yarn.lock @@ -6,7 +6,7 @@ version "12.7.11" resolved node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446 -typescript@4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" - integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== +typescript@4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4" + integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== diff --git a/javascript/extractor/src/com/semmle/js/dependencies/Fetcher.java b/javascript/extractor/src/com/semmle/js/dependencies/Fetcher.java index fa996f1b34e..d094f05653d 100644 --- a/javascript/extractor/src/com/semmle/js/dependencies/Fetcher.java +++ b/javascript/extractor/src/com/semmle/js/dependencies/Fetcher.java @@ -141,8 +141,9 @@ public class Fetcher { entryPath = entryPath.subpath(1, entryPath.getNameCount()); String filename = entryPath.getFileName().toString(); - if (!filename.endsWith(".d.ts") && !filename.equals("package.json")) { - continue; // Only extract .d.ts files and package.json + if (!filename.endsWith(".d.ts") && !filename.endsWith(".d.mts") && !filename.endsWith(".d.cts") + && !filename.equals("package.json")) { + continue; // Only extract .d.ts, .d.mts, .d.cts files, and package.json } relativePaths.add(entryPath); Path outputFile = destDir.resolve(entryPath); diff --git a/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java index 1e9b7d6ad84..bc30d83690d 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java @@ -203,7 +203,7 @@ public class FileExtractor { } }, - TYPESCRIPT(".ts", ".tsx") { + TYPESCRIPT(".ts", ".tsx", ".mts", ".cts") { @Override protected boolean contains(File f, String lcExt, ExtractorConfig config) { if (config.getTypeScriptMode() == TypeScriptMode.NONE) return false; diff --git a/javascript/extractor/src/com/semmle/js/extractor/Main.java b/javascript/extractor/src/com/semmle/js/extractor/Main.java index 5b425ab8af9..e5196ae1181 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/Main.java +++ b/javascript/extractor/src/com/semmle/js/extractor/Main.java @@ -43,7 +43,7 @@ public class Main { * A version identifier that should be updated every time the extractor changes in such a way that * it may produce different tuples for the same file under the same {@link ExtractorConfig}. */ - public static final String EXTRACTOR_VERSION = "2022-02-22"; + public static final String EXTRACTOR_VERSION = "2022-05-24"; public static final Pattern NEWLINE = Pattern.compile("\n"); diff --git a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointFeatures.qll b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointFeatures.qll index ca7f12b7a33..f146f569684 100644 --- a/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointFeatures.qll +++ b/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/EndpointFeatures.qll @@ -144,9 +144,9 @@ private module AccessPaths { not param = base.getReceiver() | result = param and - name = param.getAnImmediateUse().asExpr().(Parameter).getName() + name = param.asSource().asExpr().(Parameter).getName() or - param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and + param.asSource().asExpr() instanceof DestructuringPattern and result = param.getMember(name) ) } diff --git a/javascript/ql/lib/change-notes/2022-05-24-ecmascript-2022.md b/javascript/ql/lib/change-notes/2022-05-24-ecmascript-2022.md new file mode 100644 index 00000000000..389b7c9044b --- /dev/null +++ b/javascript/ql/lib/change-notes/2022-05-24-ecmascript-2022.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* All new ECMAScript 2022 features are now supported. diff --git a/javascript/ql/lib/change-notes/2022-05-24-typescript-4-7.md b/javascript/ql/lib/change-notes/2022-05-24-typescript-4-7.md new file mode 100644 index 00000000000..16fe46c675f --- /dev/null +++ b/javascript/ql/lib/change-notes/2022-05-24-typescript-4-7.md @@ -0,0 +1,4 @@ +--- +category: majorAnalysis +--- +* Added support for TypeScript 4.7. diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 51101e3182a..205ff21d1fd 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -2,11 +2,7 @@ * Provides an implementation of _API graphs_, which are an abstract representation of the API * surface used and/or defined by a code base. * - * The nodes of the API graph represent definitions and uses of API components. The edges are - * directed and labeled; they specify how the components represented by nodes relate to each other. - * For example, if one of the nodes represents a definition of an API function, then there - * will be nodes corresponding to the function's parameters, which are connected to the function - * node by edges labeled `parameter `. + * See `API::Node` for more in-depth documentation. */ import javascript @@ -14,50 +10,159 @@ private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps private import internal.CachedStages /** - * Provides classes and predicates for working with APIs defined or used in a database. + * Provides classes and predicates for working with the API boundary between the current + * codebase and external libraries. + * + * See `API::Node` for more in-depth documentation. */ module API { /** - * An abstract representation of a definition or use of an API component such as a function - * exported by an npm package, a parameter of such a function, or its result. + * A node in the API graph, representing a value that has crossed the boundary between this + * codebase and an external library (or in general, any external codebase). + * + * ### Basic usage + * + * API graphs are typically used to identify "API calls", that is, calls to an external function + * whose implementation is not necessarily part of the current codebase. + * + * The most basic use of API graphs is typically as follows: + * 1. Start with `API::moduleImport` for the relevant library. + * 2. Follow up with a chain of accessors such as `getMember` describing how to get to the relevant API function. + * 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`. + * + * For example, a simplified way to get arguments to `underscore.extend` would be + * ```ql + * API::moduleImport("underscore").getMember("extend").getParameter(0).asSink() + * ``` + * + * The most commonly used accessors are `getMember`, `getParameter`, and `getReturn`. + * + * ### API graph nodes + * + * There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value: + * - **Use-nodes** represent values held by the current codebase, which came from an external library. + * (The current codebase is "using" a value that came from the library). + * - **Def-nodes** represent values held by the external library, which came from this codebase. + * (The current codebase "defines" the value seen by the library). + * + * API graph nodes are associated with data-flow nodes in the current codebase. + * (Since external libraries are not part of the database, there is no way to associate with concrete + * data-flow nodes from the external library). + * - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase, + * such as the return value of a call to an external function. + * - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase, + * such as an argument passed in a call to an external function. + * + * + * ### Access paths and edge labels + * + * Nodes in the API graph are associated with a set of access paths, describing a series of operations + * that may be performed to obtain that value. + * + * For example, the access path `API::moduleImport("lodash").getMember("extend")` represents the action of + * importing `lodash` and then accessing the member `extend` on the resulting object. + * It would be associated with an expression such as `require("lodash").extend`. + * + * Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node + * determines who is performing the operation, and the type of the `B` node determines who ends up holding + * the result: + * - An edge starting from a use-node describes what the current codebase is doing to a value that + * came from a library. + * - An edge starting from a def-node describes what the external library might do to a value that + * came from the current codebase. + * - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node). + * - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is + * the place where it was "last seen" in the current codebase before flowing out) + * + * Because the implementation of the external library is not visible, it is not known exactly what operations + * it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would + * lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform + * those operations. (When constructing these edges, we assume the library is somewhat well-behaved). + * + * For example, given this snippet: + * ```js + * require('foo')(x => { doSomething(x) }) + * ``` + * A callback is passed to the external function `foo`. We can't know if `foo` will actually invoke this callback. + * But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter. + * For that reason, an edge is generated representing the argument-passing operation that might be performed by `foo`. + * This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x`. + * + * ### Thinking in operations versus code patterns + * + * Treating edges as "operations" helps avoid a pitfall in which library models become overly specific to certain code patterns. + * Consider the following two equivalent calls to `foo`: + * ```js + * const foo = require('foo'); + * + * foo({ + * myMethod(x) {...} + * }); + * + * foo({ + * get myMethod() { + * return function(x) {...} + * } + * }); + * ``` + * If `foo` calls `myMethod` on its first parameter, either of the `myMethod` implementations will be invoked. + * And indeed, the access path `API::moduleImport("foo").getParameter(0).getMember("myMethod").getParameter(0)` correctly + * identifies both `x` parameters. + * + * Observe how `getMember("myMethod")` behaves when the member is defined via a getter. When thinking in code patterns, + * it might seem obvious that `getMember` should have obtained a reference to the getter method itself. + * But when seeing it as an access to `myMethod` performed by the library, we can deduce that the relevant expression + * on the client side is actually the return-value of the getter. + * + * Although one may think of API graphs as a tool to find certain program elements in the codebase, + * it can lead to some situations where intuition does not match what works best in practice. */ class Node extends Impl::TApiNode { /** - * Gets a data-flow node corresponding to a use of the API component represented by this node. + * Get a data-flow node where this value may flow after entering the current codebase. * - * For example, `require('fs').readFileSync` is a use of the function `readFileSync` from the - * `fs` module, and `require('fs').readFileSync(file)` is a use of the return of that function. - * - * This includes indirect uses found via data flow, meaning that in - * `f(obj.foo); function f(x) {};` both `obj.foo` and `x` are uses of the `foo` member from `obj`. - * - * As another example, in the assignment `exports.plusOne = (x) => x+1` the two references to - * `x` are uses of the first parameter of `plusOne`. + * This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow. + * See `asSource()` for examples. */ pragma[inline] - DataFlow::Node getAUse() { - exists(DataFlow::SourceNode src | Impl::use(this, src) | - Impl::trackUseNode(src).flowsTo(result) - ) + DataFlow::Node getAValueReachableFromSource() { + Impl::trackUseNode(this.asSource()).flowsTo(result) } /** - * Gets an immediate use of the API component represented by this node. + * Get a data-flow node where this value enters the current codebase. * - * For example, `require('fs').readFileSync` is a an immediate use of the `readFileSync` member - * from the `fs` module. + * For example: + * ```js + * // API::moduleImport("fs").asSource() + * require('fs'); * - * Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses - * found via data flow. This means that in `const x = fs.readFile` only `fs.readFile` is a reference - * to the `readFile` member of `fs`, neither `x` nor any node that `x` flows to is a reference to - * this API component. + * // API::moduleImport("fs").getMember("readFile").asSource() + * require('fs').readFile; + * + * // API::moduleImport("fs").getMember("readFile").getReturn().asSource() + * require('fs').readFile(); + * + * require('fs').readFile( + * filename, + * // 'y' matched by API::moduleImport("fs").getMember("readFile").getParameter(1).getParameter(0).asSource() + * y => { + * ... + * }); + * ``` */ - DataFlow::SourceNode getAnImmediateUse() { Impl::use(this, result) } + DataFlow::SourceNode asSource() { Impl::use(this, result) } + + /** DEPRECATED. This predicate has been renamed to `asSource`. */ + deprecated DataFlow::SourceNode getAnImmediateUse() { result = this.asSource() } + + /** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource`. */ + deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() } /** * Gets a call to the function represented by this API component. */ - CallNode getACall() { result = this.getReturn().getAnImmediateUse() } + CallNode getACall() { result = this.getReturn().asSource() } /** * Gets a call to the function represented by this API component, @@ -72,7 +177,7 @@ module API { /** * Gets a `new` call to the function represented by this API component. */ - NewNode getAnInstantiation() { result = this.getInstance().getAnImmediateUse() } + NewNode getAnInstantiation() { result = this.getInstance().asSource() } /** * Gets an invocation (with our without `new`) to the function represented by this API component. @@ -80,26 +185,38 @@ module API { InvokeNode getAnInvocation() { result = this.getACall() or result = this.getAnInstantiation() } /** - * Gets a data-flow node corresponding to the right-hand side of a definition of the API - * component represented by this node. + * Get a data-flow node where this value leaves the current codebase and flows into an + * external library (or in general, any external codebase). * - * For example, in the assignment `exports.plusOne = (x) => x+1`, the function expression - * `(x) => x+1` is the right-hand side of the definition of the member `plusOne` of - * the enclosing module, and the expression `x+1` is the right-had side of the definition of - * its result. + * Concretely, this is either an argument passed to a call to external code, + * or the right-hand side of a property write on an object flowing into such a call. * - * Note that for parameters, it is the arguments flowing into that parameter that count as - * right-hand sides of the definition, not the declaration of the parameter itself. - * Consequently, in `require('fs').readFileSync(file)`, `file` is the right-hand - * side of a definition of the first parameter of `readFileSync` from the `fs` module. + * For example: + * ```js + * // 'x' is matched by API::moduleImport("foo").getParameter(0).asSink() + * require('foo')(x); + * + * // 'x' is matched by API::moduleImport("foo").getParameter(0).getMember("prop").asSink() + * require('foo')({ + * prop: x + * }); + * ``` */ - DataFlow::Node getARhs() { Impl::rhs(this, result) } + DataFlow::Node asSink() { Impl::rhs(this, result) } /** - * Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition - * of the API component represented by this node. + * Get a data-flow node that transitively flows to an external library (or in general, any external codebase). + * + * This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow. + * See `asSink()` for examples. */ - DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) } + DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) } + + /** DEPRECATED. This predicate has been renamed to `asSink`. */ + deprecated DataFlow::Node getARhs() { result = this.asSink() } + + /** DEPRECATED. This predicate has been renamed to `getAValueReachingSink`. */ + deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() } /** * Gets a node representing member `m` of this API component. @@ -334,7 +451,7 @@ module API { * In other words, the value of a use of `that` may flow into the right-hand side of a * definition of this node. */ - predicate refersTo(Node that) { this.getARhs() = that.getAUse() } + predicate refersTo(Node that) { this.asSink() = that.getAValueReachableFromSource() } /** * Gets the data-flow node that gives rise to this node, if any. @@ -445,11 +562,17 @@ module API { bindingset[this] EntryPoint() { any() } - /** Gets a data-flow node that uses this entry point. */ - abstract DataFlow::SourceNode getAUse(); + /** DEPRECATED. This predicate has been renamed to `getASource`. */ + deprecated DataFlow::SourceNode getAUse() { none() } - /** Gets a data-flow node that defines this entry point. */ - abstract DataFlow::Node getARhs(); + /** DEPRECATED. This predicate has been renamed to `getASink`. */ + deprecated DataFlow::SourceNode getARhs() { none() } + + /** Gets a data-flow node where a value enters the current codebase through this entry-point. */ + DataFlow::SourceNode getASource() { none() } + + /** Gets a data-flow node where a value leaves the current codebase through this entry-point. */ + DataFlow::Node getASink() { none() } /** Gets an API-node for this entry point. */ API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) } @@ -567,7 +690,7 @@ module API { base = MkRoot() and exists(EntryPoint e | lbl = Label::entryPoint(e) and - rhs = e.getARhs() + rhs = e.getASink() ) or exists(string m, string prop | @@ -744,7 +867,7 @@ module API { base = MkRoot() and exists(EntryPoint e | lbl = Label::entryPoint(e) and - ref = e.getAUse() + ref = e.getASource() ) or // property reads @@ -1178,8 +1301,8 @@ module API { API::Node callee; InvokeNode() { - this = callee.getReturn().getAnImmediateUse() or - this = callee.getInstance().getAnImmediateUse() or + this = callee.getReturn().asSource() or + this = callee.getInstance().asSource() or this = Impl::getAPromisifiedInvocation(callee, _, _) } @@ -1194,7 +1317,7 @@ module API { * Gets an API node where a RHS of the node is the `i`th argument to this call. */ pragma[noinline] - private Node getAParameterCandidate(int i) { result.getARhs() = this.getArgument(i) } + private Node getAParameterCandidate(int i) { result.asSink() = this.getArgument(i) } /** Gets the API node for a parameter of this invocation. */ Node getAParameter() { result = this.getParameter(_) } @@ -1205,13 +1328,13 @@ module API { /** Gets the API node for the return value of this call. */ Node getReturn() { result = callee.getReturn() and - result.getAnImmediateUse() = this + result.asSource() = this } /** Gets the API node for the object constructed by this invocation. */ Node getInstance() { result = callee.getInstance() and - result.getAnImmediateUse() = this + result.asSource() = this } } diff --git a/javascript/ql/lib/semmle/javascript/Arrays.qll b/javascript/ql/lib/semmle/javascript/Arrays.qll index 0433b1b5681..c129e9a31b2 100644 --- a/javascript/ql/lib/semmle/javascript/Arrays.qll +++ b/javascript/ql/lib/semmle/javascript/Arrays.qll @@ -75,7 +75,7 @@ module ArrayTaintTracking { succ.(DataFlow::SourceNode).getAMethodCall("splice") = call or // `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`. - call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice"]) and + call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice", "at"]) and succ = call or // `e = Array.from(x)`: if `x` is tainted, then so is `e`. @@ -199,13 +199,13 @@ private module ArrayDataFlow { } /** - * A step for retrieving an element from an array using `.pop()` or `.shift()`. + * A step for retrieving an element from an array using `.pop()`, `.shift()`, or `.at()`. * E.g. `array.pop()`. */ private class ArrayPopStep extends DataFlow::SharedFlowStep { override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) { exists(DataFlow::MethodCallNode call | - call.getMethodName() = ["pop", "shift"] and + call.getMethodName() = ["pop", "shift", "at"] and prop = arrayElement() and obj = call.getReceiver() and element = call diff --git a/javascript/ql/lib/semmle/javascript/JsonParsers.qll b/javascript/ql/lib/semmle/javascript/JsonParsers.qll index 35a227c3ad8..e3f1f285a21 100644 --- a/javascript/ql/lib/semmle/javascript/JsonParsers.qll +++ b/javascript/ql/lib/semmle/javascript/JsonParsers.qll @@ -29,7 +29,7 @@ private class PlainJsonParserCall extends JsonParserCall { callee = DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"], "parse") or - callee = API::moduleImport("replicator").getInstance().getMember("decode").getAnImmediateUse() or + callee = API::moduleImport("replicator").getInstance().getMember("decode").asSource() or callee = DataFlow::moduleImport("parse-json") or callee = DataFlow::moduleImport("json-parse-better-errors") or callee = DataFlow::moduleImport("json-safe-parse") or diff --git a/javascript/ql/lib/semmle/javascript/JsonSchema.qll b/javascript/ql/lib/semmle/javascript/JsonSchema.qll index bf45bcdd7b4..a298abfd2dd 100644 --- a/javascript/ql/lib/semmle/javascript/JsonSchema.qll +++ b/javascript/ql/lib/semmle/javascript/JsonSchema.qll @@ -134,7 +134,7 @@ module JsonSchema { .ref() .getMember(["addSchema", "validate", "compile", "compileAsync"]) .getParameter(0) - .getARhs() + .asSink() } } } @@ -184,7 +184,7 @@ module JsonSchema { override boolean getPolarity() { none() } override DataFlow::Node getAValidationResultAccess(boolean polarity) { - result = this.getReturn().getMember("error").getAnImmediateUse() and + result = this.getReturn().getMember("error").asSource() and polarity = false } } diff --git a/javascript/ql/lib/semmle/javascript/JsonStringifiers.qll b/javascript/ql/lib/semmle/javascript/JsonStringifiers.qll index 03ca152471f..f4cdda89e31 100644 --- a/javascript/ql/lib/semmle/javascript/JsonStringifiers.qll +++ b/javascript/ql/lib/semmle/javascript/JsonStringifiers.qll @@ -14,7 +14,7 @@ class JsonStringifyCall extends DataFlow::CallNode { callee = DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"], "stringify") or - callee = API::moduleImport("replicator").getInstance().getMember("encode").getAnImmediateUse() or + callee = API::moduleImport("replicator").getInstance().getMember("encode").asSource() or callee = DataFlow::moduleImport([ "json-stringify-safe", "json-stable-stringify", "stringify-object", diff --git a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll index 6c51b487f43..607c55c9d02 100644 --- a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll +++ b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll @@ -229,10 +229,10 @@ module MembershipCandidate { membersNode = inExpr.getRightOperand() ) or - exists(MethodCallExpr hasOwn | - this = hasOwn.getArgument(0).flow() and - test = hasOwn and - hasOwn.calls(membersNode, "hasOwnProperty") + exists(HasOwnPropertyCall hasOwn | + this = hasOwn.getProperty() and + test = hasOwn.asExpr() and + membersNode = hasOwn.getObject().asExpr() ) } diff --git a/javascript/ql/lib/semmle/javascript/NodeJS.qll b/javascript/ql/lib/semmle/javascript/NodeJS.qll index 038ca7b2186..c937a730f2d 100644 --- a/javascript/ql/lib/semmle/javascript/NodeJS.qll +++ b/javascript/ql/lib/semmle/javascript/NodeJS.qll @@ -3,6 +3,7 @@ import javascript private import NodeModuleResolutionImpl private import semmle.javascript.DynamicPropertyAccess as DynamicPropertyAccess +private import semmle.javascript.internal.CachedStages /** * A Node.js module. @@ -113,6 +114,7 @@ class NodeModule extends Module { } override DataFlow::Node getABulkExportedNode() { + Stages::Imports::ref() and exists(DataFlow::PropWrite write | write.getBase().asExpr() = this.getModuleVariable().getAnAccess() and write.getPropertyName() = "exports" and diff --git a/javascript/ql/lib/semmle/javascript/StandardLibrary.qll b/javascript/ql/lib/semmle/javascript/StandardLibrary.qll index d99cd0e7e0d..9366c76d9cc 100644 --- a/javascript/ql/lib/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/lib/semmle/javascript/StandardLibrary.qll @@ -192,3 +192,35 @@ class StringSplitCall extends DataFlow::MethodCallNode { bindingset[i] DataFlow::Node getASubstringRead(int i) { result = this.getAPropertyRead(i.toString()) } } + +/** + * A call to `Object.prototype.hasOwnProperty`, `Object.hasOwn`, or a library that implements + * the same functionality. + */ +class HasOwnPropertyCall extends DataFlow::Node instanceof DataFlow::CallNode { + DataFlow::Node object; + DataFlow::Node property; + + HasOwnPropertyCall() { + // Make sure we handle reflective calls since libraries love to do that. + super.getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() = + "hasOwnProperty" and + object = super.getReceiver() and + property = super.getArgument(0) + or + this = + [ + DataFlow::globalVarRef("Object").getAMemberCall("hasOwn"), // + DataFlow::moduleImport("has").getACall(), // + LodashUnderscore::member("has").getACall() + ] and + object = super.getArgument(0) and + property = super.getArgument(1) + } + + /** Gets the object whose property is being checked. */ + DataFlow::Node getObject() { result = object } + + /** Gets the property being checked. */ + DataFlow::Node getProperty() { result = property } +} diff --git a/javascript/ql/lib/semmle/javascript/TypeScript.qll b/javascript/ql/lib/semmle/javascript/TypeScript.qll index 70a1ed98830..2a0ce9464fb 100644 --- a/javascript/ql/lib/semmle/javascript/TypeScript.qll +++ b/javascript/ql/lib/semmle/javascript/TypeScript.qll @@ -1286,6 +1286,8 @@ class ExpressionWithTypeArguments extends @expression_with_type_arguments, Expr override ControlFlowNode getFirstControlFlowNode() { result = this.getExpression().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "ExpressionWithTypeArguments" } } /** diff --git a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll index dde1c004946..0f82ec2e8e6 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll @@ -1027,18 +1027,16 @@ module TaintTracking { class WhitelistContainmentCallSanitizer extends AdditionalSanitizerGuardNode, DataFlow::MethodCallNode { WhitelistContainmentCallSanitizer() { - exists(string name | - name = "contains" or - name = "has" or - name = "hasOwnProperty" - | - this.getMethodName() = name - ) + this.getMethodName() = ["contains", "has", "hasOwnProperty", "hasOwn"] } override predicate sanitizes(boolean outcome, Expr e) { - outcome = true and - e = this.getArgument(0).asExpr() + exists(int propertyIndex | + if this.getMethodName() = "hasOwn" then propertyIndex = 1 else propertyIndex = 0 + | + outcome = true and + e = this.getArgument(propertyIndex).asExpr() + ) } override predicate appliesTo(Configuration cfg) { any() } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Babel.qll b/javascript/ql/lib/semmle/javascript/frameworks/Babel.qll index 3d061eb2aef..4280862c6e0 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Babel.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Babel.qll @@ -198,7 +198,7 @@ module Babel { .getMember(["transform", "transformSync", "transformAsync"]) .getACall() and pred = call.getArgument(0) and - succ = [call, call.getParameter(2).getParameter(0).getAnImmediateUse()] + succ = [call, call.getParameter(2).getParameter(0).asSource()] ) } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll b/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll index 7c8e4a040a4..fdc820861b1 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll @@ -14,7 +14,7 @@ module Cheerio { } /** Gets a reference to the `cheerio` function, possibly with a loaded DOM. */ - DataFlow::SourceNode cheerioRef() { result = cheerioApi().getAUse() } + DataFlow::SourceNode cheerioRef() { result = cheerioApi().getAValueReachableFromSource() } /** * A creation of `cheerio` object, a collection of virtual DOM elements diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ClassValidator.qll b/javascript/ql/lib/semmle/javascript/frameworks/ClassValidator.qll index 381451c393c..35f966217bd 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/ClassValidator.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/ClassValidator.qll @@ -39,7 +39,8 @@ module ClassValidator { /** Holds if the given field has a decorator that sanitizes its value for the purpose of taint tracking. */ predicate isFieldSanitizedByDecorator(FieldDefinition field) { - field.getADecorator().getExpression().flow() = sanitizingDecorator().getReturn().getAUse() + field.getADecorator().getExpression().flow() = + sanitizingDecorator().getReturn().getAValueReachableFromSource() } pragma[noinline] diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll index e86af94463f..8e56a36b9bf 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll @@ -265,7 +265,7 @@ module ClientRequest { or responseType = this.getResponseType() and promise = false and - result = this.getReturn().getPromisedError().getMember("response").getAnImmediateUse() + result = this.getReturn().getPromisedError().getMember("response").asSource() } } @@ -463,7 +463,7 @@ module ClientRequest { */ private API::Node netSocketInstantiation(DataFlow::NewNode socket) { result = API::moduleImport("net").getMember("Socket").getInstance() and - socket = result.getAnImmediateUse() + socket = result.asSource() } /** @@ -827,7 +827,7 @@ module ClientRequest { class ApolloClientRequest extends ClientRequest::Range, API::InvokeNode { ApolloClientRequest() { this = apolloUriCallee().getAnInvocation() } - override DataFlow::Node getUrl() { result = this.getParameter(0).getMember("uri").getARhs() } + override DataFlow::Node getUrl() { result = this.getParameter(0).getMember("uri").asSink() } override DataFlow::Node getHost() { none() } @@ -848,10 +848,10 @@ module ClientRequest { override DataFlow::Node getUrl() { result = this.getArgument(0) } - override DataFlow::Node getHost() { result = this.getParameter(0).getMember("host").getARhs() } + override DataFlow::Node getHost() { result = this.getParameter(0).getMember("host").asSink() } override DataFlow::Node getADataNode() { - result = form.getMember("append").getACall().getParameter(1).getARhs() + result = form.getMember("append").getACall().getParameter(1).asSink() } } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll b/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll index b6892b5aa49..164bc6e8f88 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll @@ -21,7 +21,7 @@ private class CredentialsFromModel extends CredentialsExpr { string kind; CredentialsFromModel() { - this = ModelOutput::getASinkNode("credentials[" + kind + "]").getARhs().asExpr() + this = ModelOutput::getASinkNode("credentials[" + kind + "]").asSink().asExpr() } override string getCredentialsKind() { result = kind } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/D3.qll b/javascript/ql/lib/semmle/javascript/frameworks/D3.qll index 1dda09dedb8..76bdeb1324a 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/D3.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/D3.qll @@ -9,9 +9,7 @@ module D3 { private class D3GlobalEntry extends API::EntryPoint { D3GlobalEntry() { this = "D3GlobalEntry" } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("d3") } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("d3") } } /** Gets an API node referring to the `d3` module. */ @@ -71,18 +69,18 @@ module D3 { D3XssSink() { exists(API::Node htmlArg | htmlArg = d3Selection().getMember("html").getParameter(0) and - this = [htmlArg, htmlArg.getReturn()].getARhs() + this = [htmlArg, htmlArg.getReturn()].asSink() ) } } private class D3DomValueSource extends DOM::DomValueSource::Range { D3DomValueSource() { - this = d3Selection().getMember("each").getReceiver().getAnImmediateUse() + this = d3Selection().getMember("each").getReceiver().asSource() or - this = d3Selection().getMember("node").getReturn().getAnImmediateUse() + this = d3Selection().getMember("node").getReturn().asSource() or - this = d3Selection().getMember("nodes").getReturn().getUnknownMember().getAnImmediateUse() + this = d3Selection().getMember("nodes").getReturn().getUnknownMember().asSource() } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll b/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll index 1b3ee3dfea5..038f4b8afb3 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll @@ -56,13 +56,13 @@ module Electron { } } - private API::Node browserObject() { result.getAnImmediateUse() instanceof NewBrowserObject } + private API::Node browserObject() { result.asSource() instanceof NewBrowserObject } /** * A data flow node whose value may originate from a browser object instantiation. */ private class BrowserObjectByFlow extends BrowserObject { - BrowserObjectByFlow() { browserObject().getAUse() = this } + BrowserObjectByFlow() { browserObject().getAValueReachableFromSource() = this } } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Files.qll b/javascript/ql/lib/semmle/javascript/frameworks/Files.qll index 0bad9367b7f..f03f5ee1458 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Files.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Files.qll @@ -89,7 +89,7 @@ private API::Node globbyFileNameSource() { * A file name or an array of file names from the `globby` library. */ private class GlobbyFileNameSource extends FileNameSource { - GlobbyFileNameSource() { this = globbyFileNameSource().getAnImmediateUse() } + GlobbyFileNameSource() { this = globbyFileNameSource().asSource() } } /** Gets a file name or an array of file names from the `fast-glob` library. */ @@ -116,7 +116,7 @@ private API::Node fastGlobFileName() { * A file name or an array of file names from the `fast-glob` library. */ private class FastGlobFileNameSource extends FileNameSource { - FastGlobFileNameSource() { this = fastGlobFileName().getAnImmediateUse() } + FastGlobFileNameSource() { this = fastGlobFileName().asSource() } } /** @@ -200,7 +200,7 @@ private class RecursiveReadDir extends FileSystemAccess, FileNameProducer, API:: override DataFlow::Node getAPathArgument() { result = this.getArgument(0) } - override DataFlow::Node getAFileName() { result = this.trackFileSource().getAnImmediateUse() } + override DataFlow::Node getAFileName() { result = this.trackFileSource().asSource() } private API::Node trackFileSource() { result = this.getParameter([1 .. 2]).getParameter(1) @@ -223,7 +223,7 @@ private module JsonFile { override DataFlow::Node getAPathArgument() { result = this.getArgument(0) } - override DataFlow::Node getADataNode() { result = this.trackRead().getAnImmediateUse() } + override DataFlow::Node getADataNode() { result = this.trackRead().asSource() } private API::Node trackRead() { this.getCalleeName() = "readFile" and @@ -272,7 +272,7 @@ private class LoadJsonFile extends FileSystemReadAccess, API::CallNode { override DataFlow::Node getAPathArgument() { result = this.getArgument(0) } - override DataFlow::Node getADataNode() { result = this.trackRead().getAnImmediateUse() } + override DataFlow::Node getADataNode() { result = this.trackRead().asSource() } private API::Node trackRead() { this.getCalleeName() = "sync" and result = this.getReturn() @@ -310,7 +310,7 @@ private class WalkDir extends FileNameProducer, FileSystemAccess, API::CallNode override DataFlow::Node getAPathArgument() { result = this.getArgument(0) } - override DataFlow::Node getAFileName() { result = this.trackFileSource().getAnImmediateUse() } + override DataFlow::Node getAFileName() { result = this.trackFileSource().asSource() } private API::Node trackFileSource() { not this.getCalleeName() = ["sync", "async"] and diff --git a/javascript/ql/lib/semmle/javascript/frameworks/FormParsers.qll b/javascript/ql/lib/semmle/javascript/frameworks/FormParsers.qll index c5fa208406f..26e0d4fe94f 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/FormParsers.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/FormParsers.qll @@ -15,7 +15,7 @@ private class BusBoyRemoteFlow extends RemoteFlowSource { .getMember("on") .getParameter(1) .getAParameter() - .getAnImmediateUse() + .asSource() } override string getSourceType() { result = "parsed user value from Busbuy" } @@ -49,12 +49,12 @@ private class MultipartyRemoteFlow extends RemoteFlowSource { MultipartyRemoteFlow() { exists(API::Node form | form = API::moduleImport("multiparty").getMember("Form").getInstance() | exists(API::CallNode parse | parse = form.getMember("parse").getACall() | - this = parse.getParameter(1).getAParameter().getAnImmediateUse() + this = parse.getParameter(1).getAParameter().asSource() ) or exists(API::CallNode on | on = form.getMember("on").getACall() | on.getArgument(0).mayHaveStringValue(["part", "file", "field"]) and - this = on.getParameter(1).getAParameter().getAnImmediateUse() + this = on.getParameter(1).getAParameter().asSource() ) ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/History.qll b/javascript/ql/lib/semmle/javascript/frameworks/History.qll index b4535b9bf2b..37c0057f6c1 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/History.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/History.qll @@ -8,9 +8,7 @@ module History { private class HistoryGlobalEntry extends API::EntryPoint { HistoryGlobalEntry() { this = "HistoryLibrary" } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("HistoryLibrary") } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("HistoryLibrary") } } /** @@ -40,11 +38,11 @@ module History { HistoryLibraryRemoteFlow() { exists(API::Node loc | loc = [getBrowserHistory(), getHashHistory()].getMember("location") | - this = loc.getMember("hash").getAnImmediateUse() and kind.isFragment() + this = loc.getMember("hash").asSource() and kind.isFragment() or - this = loc.getMember("pathname").getAnImmediateUse() and kind.isPath() + this = loc.getMember("pathname").asSource() and kind.isPath() or - this = loc.getMember("search").getAnImmediateUse() and kind.isQuery() + this = loc.getMember("search").asSource() and kind.isQuery() ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/HttpProxy.qll b/javascript/ql/lib/semmle/javascript/frameworks/HttpProxy.qll index 2467ca0973b..24e694ca8d9 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/HttpProxy.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/HttpProxy.qll @@ -19,10 +19,10 @@ private module HttpProxy { .getACall() } - override DataFlow::Node getUrl() { result = getParameter(0).getMember("target").getARhs() } + override DataFlow::Node getUrl() { result = getParameter(0).getMember("target").asSink() } override DataFlow::Node getHost() { - result = getParameter(0).getMember("target").getMember("host").getARhs() + result = getParameter(0).getMember("target").getMember("host").asSink() } override DataFlow::Node getADataNode() { none() } @@ -49,10 +49,10 @@ private module HttpProxy { ) } - override DataFlow::Node getUrl() { result = getOptionsObject().getMember("target").getARhs() } + override DataFlow::Node getUrl() { result = getOptionsObject().getMember("target").asSink() } override DataFlow::Node getHost() { - result = getOptionsObject().getMember("target").getMember("host").getARhs() + result = getOptionsObject().getMember("target").getMember("host").asSink() } override DataFlow::Node getADataNode() { none() } @@ -78,8 +78,8 @@ private module HttpProxy { ProxyListenerCallback() { exists(API::CallNode call | call = any(CreateServerCall server).getReturn().getMember(["on", "once"]).getACall() and - call.getParameter(0).getARhs().mayHaveStringValue(event) and - this = call.getParameter(1).getARhs().getAFunctionValue() + call.getParameter(0).asSink().mayHaveStringValue(event) and + this = call.getParameter(1).asSink().getAFunctionValue() ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll b/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll index 39b0ea201bb..3a5ef400801 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Immutable.qll @@ -16,9 +16,7 @@ private module Immutable { private class ImmutableGlobalEntry extends API::EntryPoint { ImmutableGlobalEntry() { this = "ImmutableGlobalEntry" } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("Immutable") } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("Immutable") } } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Knex.qll b/javascript/ql/lib/semmle/javascript/frameworks/Knex.qll index 6f2098f27c9..e768a9feaff 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Knex.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Knex.qll @@ -69,7 +69,7 @@ module Knex { private class KnexDatabaseAwait extends DatabaseAccess, DataFlow::ValueNode { KnexDatabaseAwait() { exists(AwaitExpr enclosingAwait | this = enclosingAwait.flow() | - enclosingAwait.getOperand() = knexObject().getAUse().asExpr() + enclosingAwait.getOperand() = knexObject().getAValueReachableFromSource().asExpr() ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll b/javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll index 853b76afdda..1118f05b506 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/LdapJS.qll @@ -61,10 +61,10 @@ module LdapJS { SearchFilter() { options = ldapClient().getMember("search").getACall().getParameter(1) and - this = options.getARhs() + this = options.asSink() } - override DataFlow::Node getInput() { result = options.getMember("filter").getARhs() } + override DataFlow::Node getInput() { result = options.getMember("filter").asSink() } override DataFlow::Node getOutput() { result = this } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/LiveServer.qll b/javascript/ql/lib/semmle/javascript/frameworks/LiveServer.qll index aa365680cd2..1d7604e0b0c 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/LiveServer.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/LiveServer.qll @@ -12,7 +12,7 @@ private module LiveServer { class ServerDefinition extends HTTP::Servers::StandardServerDefinition { ServerDefinition() { this = DataFlow::moduleImport("live-server").asExpr() } - API::Node getImportNode() { result.getAnImmediateUse().asExpr() = this } + API::Node getImportNode() { result.asSource().asExpr() = this } } /** @@ -41,7 +41,7 @@ private module LiveServer { override DataFlow::SourceNode getARouteHandler() { exists(DataFlow::SourceNode middleware | - middleware = call.getParameter(0).getMember("middleware").getAValueReachingRhs() + middleware = call.getParameter(0).getMember("middleware").getAValueReachingSink() | result = middleware.getAMemberCall(["push", "unshift"]).getArgument(0).getAFunctionValue() or diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll b/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll index aa79a35785b..8f5938f4865 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Logging.qll @@ -35,9 +35,7 @@ private module Console { private class ConsoleGlobalEntry extends API::EntryPoint { ConsoleGlobalEntry() { this = "ConsoleGlobalEntry" } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("console") } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("console") } } /** @@ -352,7 +350,7 @@ private module Pino { // `pino` is installed as the "log" property on the request object in `Express` and similar libraries. // in `Hapi` the property is "logger". exists(HTTP::RequestExpr req, API::Node reqNode | - reqNode.getAnImmediateUse() = req.flow().getALocalSource() and + reqNode.asSource() = req.flow().getALocalSource() and result = reqNode.getMember(["log", "logger"]) ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Markdown.qll b/javascript/ql/lib/semmle/javascript/frameworks/Markdown.qll index d131c70773a..492ab0cbcec 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Markdown.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Markdown.qll @@ -163,14 +163,14 @@ module Markdown { or call = API::moduleImport("markdown-it").getMember("Markdown").getAnInvocation() | - call.getParameter(0).getMember("html").getARhs().mayHaveBooleanValue(true) and + call.getParameter(0).getMember("html").asSink().mayHaveBooleanValue(true) and result = call.getReturn() ) or exists(API::CallNode call | call = markdownIt().getMember(["use", "set", "configure", "enable", "disable"]).getACall() and result = call.getReturn() and - not call.getParameter(0).getAValueReachingRhs() = + not call.getParameter(0).getAValueReachingSink() = DataFlow::moduleImport("markdown-it-sanitizer") ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll index bc2bf109fdf..1799f35beb8 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll @@ -140,11 +140,9 @@ module NestJS { private class ValidationNodeEntry extends API::EntryPoint { ValidationNodeEntry() { this = "ValidationNodeEntry" } - override DataFlow::SourceNode getAUse() { + override DataFlow::SourceNode getASource() { result.(DataFlow::ClassNode).getName() = "ValidationPipe" } - - override DataFlow::Node getARhs() { none() } } /** Gets an API node referring to the constructor of `ValidationPipe` */ @@ -181,7 +179,7 @@ module NestJS { predicate hasGlobalValidationPipe(Folder folder) { exists(DataFlow::CallNode call | call.getCalleeName() = "useGlobalPipes" and - call.getArgument(0) = validationPipe().getInstance().getAUse() and + call.getArgument(0) = validationPipe().getInstance().getAValueReachableFromSource() and folder = call.getFile().getParentContainer() ) or @@ -193,7 +191,7 @@ module NestJS { .getAMember() .getMember("useFactory") .getReturn() - .getARhs() = validationPipe().getInstance().getAUse() and + .asSink() = validationPipe().getInstance().getAValueReachableFromSource() and folder = decorator.getFile().getParentContainer() ) or @@ -204,7 +202,7 @@ module NestJS { * Holds if `param` is affected by a pipe that sanitizes inputs. */ private predicate hasSanitizingPipe(NestJSRequestInput param, boolean dependsOnType) { - param.getAPipe() = sanitizingPipe(dependsOnType).getAUse() + param.getAPipe() = sanitizingPipe(dependsOnType).getAValueReachableFromSource() or hasGlobalValidationPipe(param.getFile().getParentContainer()) and dependsOnType = true @@ -395,11 +393,11 @@ module NestJS { /** Gets a parameter with this decorator applied. */ DataFlow::ParameterNode getADecoratedParameter() { - result.getADecorator() = getReturn().getReturn().getAUse() + result.getADecorator() = getReturn().getReturn().getAValueReachableFromSource() } /** Gets a value returned by the decorator's callback, which becomes the value of the decorated parameter. */ - DataFlow::Node getResult() { result = getParameter(0).getReturn().getARhs() } + DataFlow::Node getResult() { result = getParameter(0).getReturn().asSink() } } /** @@ -427,7 +425,7 @@ module NestJS { private class ExpressRequestSource extends Express::RequestSource { ExpressRequestSource() { this.(DataFlow::ParameterNode).getADecorator() = - nestjs().getMember(["Req", "Request"]).getReturn().getAnImmediateUse() + nestjs().getMember(["Req", "Request"]).getReturn().asSource() or this = executionContext() @@ -435,7 +433,7 @@ module NestJS { .getReturn() .getMember("getRequest") .getReturn() - .getAnImmediateUse() + .asSource() } /** @@ -452,7 +450,7 @@ module NestJS { private class ExpressResponseSource extends Express::ResponseSource { ExpressResponseSource() { this.(DataFlow::ParameterNode).getADecorator() = - nestjs().getMember(["Res", "Response"]).getReturn().getAnImmediateUse() + nestjs().getMember(["Res", "Response"]).getReturn().asSource() } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Next.qll b/javascript/ql/lib/semmle/javascript/frameworks/Next.qll index 644754ce75e..091aa8ba1af 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Next.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Next.qll @@ -252,6 +252,6 @@ module NextJS { .getParameter(0) .getParameter(0) .getMember("router") - .getAnImmediateUse() + .asSource() } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll b/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll index b04467a93ec..0d2da4b10bb 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll @@ -20,7 +20,7 @@ deprecated module NoSQL = NoSql; * Gets a value that has been assigned to the "$where" property of an object that flows to `queryArg`. */ private DataFlow::Node getADollarWhereProperty(API::Node queryArg) { - result = queryArg.getMember("$where").getARhs() + result = queryArg.getMember("$where").asSink() } /** @@ -418,7 +418,7 @@ private module Mongoose { param = f.getParameter(0).getParameter(1) | exists(DataFlow::MethodCallNode pred | - // limitation: look at the previous method call + // limitation: look at the previous method call Query::MethodSignatures::returnsDocumentQuery(pred.getMethodName(), asArray) and pred.getAMethodCall() = f.getACall() ) @@ -501,7 +501,7 @@ private module Mongoose { Credentials() { exists(string prop | - this = createConnection().getParameter(3).getMember(prop).getARhs().asExpr() + this = createConnection().getParameter(3).getMember(prop).asSink().asExpr() | prop = "user" and kind = "user name" or @@ -518,7 +518,7 @@ private module Mongoose { class MongoDBQueryPart extends NoSql::Query { MongooseFunction f; - MongoDBQueryPart() { this = f.getQueryArgument().getARhs().asExpr() } + MongoDBQueryPart() { this = f.getQueryArgument().asSink().asExpr() } override DataFlow::Node getACodeOperator() { result = getADollarWhereProperty(f.getQueryArgument()) @@ -540,7 +540,7 @@ private module Mongoose { override DataFlow::Node getAQueryArgument() { // NB: the complete information is not easily accessible for deeply chained calls - f.getQueryArgument().getARhs() = result + f.getQueryArgument().asSink() = result } override DataFlow::Node getAResult() { @@ -770,7 +770,7 @@ private module Redis { RedisKeyArgument() { exists(string method, int argIndex | QuerySignatures::argumentIsAmbiguousKey(method, argIndex) and - this = redis().getMember(method).getParameter(argIndex).getARhs().asExpr() + this = redis().getMember(method).getParameter(argIndex).asSink().asExpr() ) } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll index 2adc68f907a..75e222730cc 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll @@ -739,7 +739,7 @@ module NodeJSLib { methodName = ["execFile", "execFileSync", "spawn", "spawnSync", "fork"] ) and // all of the above methods take the command as their first argument - result = this.getParameter(0).getARhs() + result = this.getParameter(0).asSink() } override DataFlow::Node getACommandArgument() { result = this.getACommandArgument(_) } @@ -751,7 +751,7 @@ module NodeJSLib { override DataFlow::Node getArgumentList() { methodName = ["execFile", "execFileSync", "fork", "spawn", "spawnSync"] and // all of the above methods take the argument list as their second argument - result = this.getParameter(1).getARhs() + result = this.getParameter(1).asSink() } override predicate isSync() { methodName.matches("%Sync") } @@ -759,7 +759,7 @@ module NodeJSLib { override DataFlow::Node getOptionsArg() { not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback not result.getALocalSource() instanceof DataFlow::ArrayCreationNode and // looks like argumentlist - not result = this.getParameter(0).getARhs() and + not result = this.getParameter(0).asSink() and // fork/spawn and all sync methos always has options as the last argument if methodName.matches("fork%") or @@ -768,7 +768,7 @@ module NodeJSLib { then result = this.getLastArgument() else // the rest (exec/execFile) has the options argument as their second last. - result = this.getParameter(this.getNumArgument() - 2).getARhs() + result = this.getParameter(this.getNumArgument() - 2).asSink() } } @@ -1070,7 +1070,7 @@ module NodeJSLib { */ private class EventEmitterSubClass extends DataFlow::ClassNode { EventEmitterSubClass() { - this.getASuperClassNode() = getAnEventEmitterImport().getAUse() or + this.getASuperClassNode() = getAnEventEmitterImport().getAValueReachableFromSource() or this.getADirectSuperClass() instanceof EventEmitterSubClass } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Prettier.qll b/javascript/ql/lib/semmle/javascript/frameworks/Prettier.qll index ec9e490159e..1277c1ee133 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Prettier.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Prettier.qll @@ -22,7 +22,7 @@ private module Prettier { call = API::moduleImport("prettier").getMember("formatWithCursor").getACall() | pred = call.getArgument(0) and - succ = call.getReturn().getMember("formatted").getAnImmediateUse() + succ = call.getReturn().getMember("formatted").asSource() ) } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Puppeteer.qll b/javascript/ql/lib/semmle/javascript/frameworks/Puppeteer.qll index b7bef025d23..0636b8603b9 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Puppeteer.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Puppeteer.qll @@ -86,7 +86,7 @@ module Puppeteer { this = page().getMember(["addStyleTag", "addScriptTag"]).getACall() } - override DataFlow::Node getUrl() { result = getParameter(0).getMember("url").getARhs() } + override DataFlow::Node getUrl() { result = getParameter(0).getMember("url").asSink() } override DataFlow::Node getHost() { none() } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll index fae5a1c76d7..67901528f2b 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll @@ -58,10 +58,10 @@ module Redux { */ class StoreCreation extends DataFlow::SourceNode instanceof StoreCreation::Range { /** Gets a reference to the store. */ - DataFlow::SourceNode ref() { result = asApiNode().getAUse() } + DataFlow::SourceNode ref() { result = asApiNode().getAValueReachableFromSource() } /** Gets an API node that refers to this store creation. */ - API::Node asApiNode() { result.getAnImmediateUse() = this } + API::Node asApiNode() { result.asSource() = this } /** Gets the data flow node holding the root reducer for this store. */ DataFlow::Node getReducerArg() { result = super.getReducerArg() } @@ -94,7 +94,7 @@ module Redux { } override DataFlow::Node getReducerArg() { - result = getParameter(0).getMember("reducer").getARhs() + result = getParameter(0).getMember("reducer").asSink() } } } @@ -106,7 +106,7 @@ module Redux { private API::Node rootState() { result instanceof RootStateSource or - stateStep(rootState().getAUse(), result.getAnImmediateUse()) + stateStep(rootState().getAValueReachableFromSource(), result.asSource()) } /** @@ -120,7 +120,7 @@ module Redux { accessPath = joinAccessPaths(base, prop) ) or - stateStep(rootStateAccessPath(accessPath).getAUse(), result.getAnImmediateUse()) + stateStep(rootStateAccessPath(accessPath).getAValueReachableFromSource(), result.asSource()) } /** @@ -193,7 +193,7 @@ module Redux { CombineReducers() { this = combineReducers().getACall() } override DataFlow::Node getStateHandlerArg(string prop) { - result = getParameter(0).getMember(prop).getARhs() + result = getParameter(0).getMember(prop).asSink() } } @@ -207,7 +207,7 @@ module Redux { */ private class NestedCombineReducers extends DelegatingReducer, DataFlow::ObjectLiteralNode { NestedCombineReducers() { - this = combineReducers().getParameter(0).getAMember+().getAValueReachingRhs() + this = combineReducers().getParameter(0).getAMember+().getAValueReachingSink() } override DataFlow::Node getStateHandlerArg(string prop) { @@ -235,7 +235,7 @@ module Redux { override DataFlow::Node getActionHandlerArg(DataFlow::Node actionType) { exists(DataFlow::PropWrite write | - result = getParameter(0).getAMember().getARhs() and + result = getParameter(0).getAMember().asSink() and write.getRhs() = result and actionType = write.getPropertyNameExpr().flow() ) @@ -374,7 +374,7 @@ module Redux { CreateSliceReducer() { call = API::moduleImport("@reduxjs/toolkit").getMember("createSlice").getACall() and - this = call.getReturn().getMember("reducer").getAnImmediateUse() + this = call.getReturn().getMember("reducer").asSource() } private API::Node getABuilderRef() { @@ -385,14 +385,14 @@ module Redux { override DataFlow::Node getActionHandlerArg(DataFlow::Node actionType) { exists(string name | - result = call.getParameter(0).getMember("reducers").getMember(name).getARhs() and - actionType = call.getReturn().getMember("actions").getMember(name).getAnImmediateUse() + result = call.getParameter(0).getMember("reducers").getMember(name).asSink() and + actionType = call.getReturn().getMember("actions").getMember(name).asSource() ) or // Properties of 'extraReducers': // { extraReducers: { [action]: reducer }} exists(DataFlow::PropWrite write | - result = call.getParameter(0).getMember("extraReducers").getAMember().getARhs() and + result = call.getParameter(0).getMember("extraReducers").getAMember().asSink() and write.getRhs() = result and actionType = write.getPropertyNameExpr().flow() ) @@ -444,8 +444,8 @@ module Redux { or // x -> bindActionCreators({ x, ... }) exists(BindActionCreatorsCall bind, string prop | - ref(t.continue()).flowsTo(bind.getParameter(0).getMember(prop).getARhs()) and - result = bind.getReturn().getMember(prop).getAnImmediateUse() + ref(t.continue()).flowsTo(bind.getParameter(0).getMember(prop).asSink()) and + result = bind.getReturn().getMember(prop).asSource() ) or // x -> combineActions(x, ...) @@ -580,11 +580,11 @@ module Redux { MultiAction() { createActions = API::moduleImport("redux-actions").getMember("createActions").getACall() and - this = createActions.getReturn().getMember(name).getAnImmediateUse() + this = createActions.getReturn().getMember(name).asSource() } override DataFlow::FunctionNode getMiddlewareFunction(boolean async) { - result.flowsTo(createActions.getParameter(0).getMember(getTypeTag()).getARhs()) and + result.flowsTo(createActions.getParameter(0).getMember(getTypeTag()).asSink()) and async = false } @@ -614,12 +614,12 @@ module Redux { CreateSliceAction() { call = API::moduleImport("@reduxjs/toolkit").getMember("createSlice").getACall() and - this = call.getReturn().getMember("actions").getMember(actionName).getAnImmediateUse() + this = call.getReturn().getMember("actions").getMember(actionName).asSource() } override string getTypeTag() { exists(string prefix | - call.getParameter(0).getMember("name").getARhs().mayHaveStringValue(prefix) and + call.getParameter(0).getMember("name").asSink().mayHaveStringValue(prefix) and result = prefix + "/" + actionName ) } @@ -640,7 +640,7 @@ module Redux { override DataFlow::FunctionNode getMiddlewareFunction(boolean async) { async = true and - result = getParameter(1).getAValueReachingRhs() + result = getParameter(1).getAValueReachingSink() } override string getTypeTag() { getArgument(0).mayHaveStringValue(result) } @@ -885,12 +885,12 @@ module Redux { accessPath = getAffectedStateAccessPath(reducer) | pred = function.getReturnNode() and - succ = rootStateAccessPath(accessPath).getAnImmediateUse() + succ = rootStateAccessPath(accessPath).asSource() or exists(string suffix, DataFlow::SourceNode base | base = [function.getParameter(0), function.getReturnNode().getALocalSource()] and pred = AccessPath::getAnAssignmentTo(base, suffix) and - succ = rootStateAccessPath(accessPath + "." + suffix).getAnImmediateUse() + succ = rootStateAccessPath(accessPath + "." + suffix).asSource() ) ) or @@ -901,7 +901,7 @@ module Redux { reducer.isRootStateHandler() and base = [function.getParameter(0), function.getReturnNode().getALocalSource()] and pred = AccessPath::getAnAssignmentTo(base, suffix) and - succ = rootStateAccessPath(suffix).getAnImmediateUse() + succ = rootStateAccessPath(suffix).asSource() ) } @@ -916,7 +916,7 @@ module Redux { */ private DataFlow::ObjectLiteralNode getAManuallyDispatchedValue(string actionType) { result.getAPropertyWrite("type").getRhs().mayHaveStringValue(actionType) and - result = getADispatchedValueNode().getAValueReachingRhs() + result = getADispatchedValueNode().getAValueReachingSink() } /** @@ -994,7 +994,7 @@ module Redux { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { exists(API::CallNode call | call = useSelector().getACall() and - pred = call.getParameter(0).getReturn().getARhs() and + pred = call.getParameter(0).getReturn().asSink() and succ = call ) } @@ -1046,19 +1046,19 @@ module Redux { // // const mapDispatchToProps = { foo } // - result = getMapDispatchToProps().getMember(name).getARhs() + result = getMapDispatchToProps().getMember(name).asSink() or // // const mapDispatchToProps = dispatch => ( { foo } ) // - result = getMapDispatchToProps().getReturn().getMember(name).getARhs() + result = getMapDispatchToProps().getReturn().getMember(name).asSink() or // Explicitly bound by bindActionCreators: // // const mapDispatchToProps = dispatch => bindActionCreators({ foo }, dispatch); // exists(BindActionCreatorsCall bind | - bind.flowsTo(getMapDispatchToProps().getReturn().getARhs()) and + bind.flowsTo(getMapDispatchToProps().getReturn().asSink()) and result = bind.getOptionArgument(0, name) ) } @@ -1096,9 +1096,7 @@ module Redux { private class HeuristicConnectEntryPoint extends API::EntryPoint { HeuristicConnectEntryPoint() { this = "react-redux-connect" } - override DataFlow::Node getARhs() { none() } - - override DataFlow::SourceNode getAUse() { + override DataFlow::SourceNode getASource() { exists(DataFlow::CallNode call | call.getAnArgument().asExpr().(Identifier).getName() = ["mapStateToProps", "mapDispatchToProps"] and @@ -1115,12 +1113,12 @@ module Redux { override API::Node getMapStateToProps() { result = getAParameter() and - result.getARhs().asExpr().(Identifier).getName() = "mapStateToProps" + result.asSink().asExpr().(Identifier).getName() = "mapStateToProps" } override API::Node getMapDispatchToProps() { result = getAParameter() and - result.getARhs().asExpr().(Identifier).getName() = "mapDispatchToProps" + result.asSink().asExpr().(Identifier).getName() = "mapDispatchToProps" } } @@ -1130,7 +1128,7 @@ module Redux { private class StateToPropsStep extends StateStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { exists(ConnectCall call | - pred = call.getMapStateToProps().getReturn().getARhs() and + pred = call.getMapStateToProps().getReturn().asSink() and succ = call.getReactComponent().getADirectPropsAccess() ) } @@ -1205,7 +1203,7 @@ module Redux { // Selector functions may be given as an array exists(DataFlow::ArrayCreationNode array | array.flowsTo(getArgument(0)) and - result.getAUse() = array.getElement(i) + result.getAValueReachableFromSource() = array.getElement(i) ) } } @@ -1221,13 +1219,13 @@ module Redux { // Return value of `i`th callback flows to the `i`th parameter of the last callback. exists(CreateSelectorCall call, int index | call.getNumArgument() > 1 and - pred = call.getSelectorFunction(index).getReturn().getARhs() and - succ = call.getLastParameter().getParameter(index).getAnImmediateUse() + pred = call.getSelectorFunction(index).getReturn().asSink() and + succ = call.getLastParameter().getParameter(index).asSource() ) or // The result of the last callback is the final result exists(CreateSelectorCall call | - pred = call.getLastParameter().getReturn().getARhs() and + pred = call.getLastParameter().getReturn().asSink() and succ = call ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll b/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll index 03c78c2561f..edd92614a2c 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll @@ -9,7 +9,7 @@ module SQL { abstract class SqlString extends Expr { } private class SqlStringFromModel extends SqlString { - SqlStringFromModel() { this = ModelOutput::getASinkNode("sql-injection").getARhs().asExpr() } + SqlStringFromModel() { this = ModelOutput::getASinkNode("sql-injection").asSink().asExpr() } } /** @@ -109,7 +109,7 @@ private module MySql { Credentials() { exists(API::Node callee, string prop | callee in [createConnection(), createPool()] and - this = callee.getParameter(0).getMember(prop).getARhs().asExpr() and + this = callee.getParameter(0).getMember(prop).asSink().asExpr() and ( prop = "user" and kind = "user name" or @@ -200,7 +200,7 @@ private module Postgres { QueryString() { this = any(QueryCall qc).getAQueryArgument().asExpr() or - this = API::moduleImport("pg-cursor").getParameter(0).getARhs().asExpr() + this = API::moduleImport("pg-cursor").getParameter(0).asSink().asExpr() } } @@ -210,9 +210,9 @@ private module Postgres { Credentials() { exists(string prop | - this = [newClient(), newPool()].getParameter(0).getMember(prop).getARhs().asExpr() + this = [newClient(), newPool()].getParameter(0).getMember(prop).asSink().asExpr() or - this = pgPromise().getParameter(0).getMember(prop).getARhs().asExpr() + this = pgPromise().getParameter(0).getMember(prop).asSink().asExpr() | prop = "user" and kind = "user name" or @@ -383,7 +383,7 @@ private module Sqlite { /** A call to a Sqlite query method. */ private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { QueryCall() { - this = getAChainingQueryCall().getAnImmediateUse() + this = getAChainingQueryCall().asSource() or this = database().getMember("prepare").getACall() } @@ -440,7 +440,8 @@ private module MsSql { override TaggedTemplateExpr astNode; QueryTemplateExpr() { - mssql().getMember("query").getAUse() = DataFlow::valueNode(astNode.getTag()) + mssql().getMember("query").getAValueReachableFromSource() = + DataFlow::valueNode(astNode.getTag()) } override DataFlow::Node getAResult() { @@ -494,7 +495,7 @@ private module MsSql { or callee = mssql().getMember("ConnectionPool") ) and - this = callee.getParameter(0).getMember(prop).getARhs().asExpr() and + this = callee.getParameter(0).getMember(prop).asSink().asExpr() and ( prop = "user" and kind = "user name" or diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Snapdragon.qll b/javascript/ql/lib/semmle/javascript/frameworks/Snapdragon.qll index d5692c3d2c2..6f460a842ff 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Snapdragon.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Snapdragon.qll @@ -27,7 +27,7 @@ private module Snapdragon { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { exists(string methodName, API::CallNode set, API::CallNode call, API::Node base | // the handler, registered with a call to `.set`. - set = getSetCall+(base.getMember(methodName + "r")).getAnImmediateUse() and + set = getSetCall+(base.getMember(methodName + "r")).asSource() and // the snapdragon instance. The API is chaining, you can also use the instance directly. base = API::moduleImport("snapdragon").getInstance() and methodName = ["parse", "compile"] and @@ -47,7 +47,7 @@ private module Snapdragon { or // for compiler handlers the input is the first parameter. methodName = "compile" and - succ = set.getParameter(1).getParameter(0).getAnImmediateUse() + succ = set.getParameter(1).getParameter(0).asSource() ) ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/SocketIO.qll b/javascript/ql/lib/semmle/javascript/frameworks/SocketIO.qll index df761420e29..e48d674fa74 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/SocketIO.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/SocketIO.qll @@ -41,7 +41,7 @@ module SocketIO { class ServerObject extends SocketIOObject { API::Node node; - ServerObject() { node = newServer() and this = node.getAnImmediateUse() } + ServerObject() { node = newServer() and this = node.asSource() } /** Gets the Api node for this server. */ API::Node asApiNode() { result = node } @@ -81,7 +81,7 @@ module SocketIO { ) } - override DataFlow::SourceNode ref() { result = this.server().getAUse() } + override DataFlow::SourceNode ref() { result = this.server().getAValueReachableFromSource() } } /** A data flow node that may produce (that is, create or return) a socket.io server. */ @@ -119,7 +119,7 @@ module SocketIO { API::Node node; NamespaceBase() { - this = node.getAnImmediateUse() and + this = node.asSource() and exists(ServerObject srv | // namespace lookup on `srv` node = srv.asApiNode().getMember("sockets") and @@ -158,7 +158,7 @@ module SocketIO { ) } - override DataFlow::SourceNode ref() { result = this.namespace().getAUse() } + override DataFlow::SourceNode ref() { result = this.namespace().getAValueReachableFromSource() } } /** A data flow node that may produce a namespace object. */ diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll b/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll index c1e6cb342f1..90adf7d7de6 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Templating.qll @@ -233,7 +233,7 @@ module Templating { /** Gets an API node that may flow to `succ` through a template instantiation. */ private API::Node getTemplateInput(DataFlow::SourceNode succ) { exists(TemplateInstantiation inst, API::Node base, string name | - base.getARhs() = inst.getTemplateParamsNode() and + base.asSink() = inst.getTemplateParamsNode() and result = base.getMember(name) and succ = inst.getTemplateFile() @@ -244,7 +244,7 @@ module Templating { ) or exists(TemplateInstantiation inst, string accessPath | - result.getARhs() = inst.getTemplateParamForValue(accessPath) and + result.asSink() = inst.getTemplateParamForValue(accessPath) and succ = inst.getTemplateFile() .getAnImportedFile*() @@ -261,7 +261,7 @@ module Templating { private class TemplateInputStep extends DataFlow::SharedFlowStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - getTemplateInput(succ).getARhs() = pred + getTemplateInput(succ).asSink() = pred } } @@ -321,8 +321,8 @@ module Templating { result = this.getStringValue() or exists(API::Node node | - this = node.getARhs() and - result = node.getAValueReachingRhs().getStringValue() + this = node.asSink() and + result = node.getAValueReachingSink().getStringValue() ) } @@ -657,11 +657,9 @@ module Templating { private class IncludeFunctionAsEntryPoint extends API::EntryPoint { IncludeFunctionAsEntryPoint() { this = "IncludeFunctionAsEntryPoint" } - override DataFlow::SourceNode getAUse() { + override DataFlow::SourceNode getASource() { result = any(TemplatePlaceholderTag tag).getInnerTopLevel().getAVariableUse("include") } - - override DataFlow::Node getARhs() { none() } } /** @@ -718,7 +716,7 @@ module Templating { override TemplateSyntax getTemplateSyntax() { result.getAPackageName() = engine } override DataFlow::SourceNode getOutput() { - result = this.getParameter([1, 2]).getParameter(1).getAnImmediateUse() + result = this.getParameter([1, 2]).getParameter(1).asSource() or not exists(this.getParameter([1, 2]).getParameter(1)) and result = this diff --git a/javascript/ql/lib/semmle/javascript/frameworks/TorrentLibraries.qll b/javascript/ql/lib/semmle/javascript/frameworks/TorrentLibraries.qll index 1724e1174aa..29c100234bf 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/TorrentLibraries.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/TorrentLibraries.qll @@ -21,7 +21,7 @@ module ParseTorrent { node = mod().getReturn() or node = mod().getMember("remote").getParameter(1).getParameter(1) ) and - this = node.getAnImmediateUse() + this = node.asSource() } /** Gets the API node for this torrent object. */ @@ -29,7 +29,9 @@ module ParseTorrent { } /** Gets a data flow node referring to a parsed torrent. */ - DataFlow::SourceNode parsedTorrentRef() { result = any(ParsedTorrent t).asApiNode().getAUse() } + DataFlow::SourceNode parsedTorrentRef() { + result = any(ParsedTorrent t).asApiNode().getAValueReachableFromSource() + } /** * An access to user-controlled torrent information. @@ -38,7 +40,7 @@ module ParseTorrent { UserControlledTorrentInfo() { exists(API::Node read | read = any(ParsedTorrent t).asApiNode().getAMember() and - this = read.getAnImmediateUse() + this = read.asSource() | exists(string prop | not ( diff --git a/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll b/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll index 8335600f9db..b1b2cc6cf0a 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/TrustedTypes.qll @@ -14,9 +14,7 @@ module TrustedTypes { private class TrustedTypesEntry extends API::EntryPoint { TrustedTypesEntry() { this = "TrustedTypesEntry" } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("trustedTypes") } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("trustedTypes") } } private API::Node trustedTypesObj() { result = any(TrustedTypesEntry entry).getANode() } @@ -38,7 +36,7 @@ module TrustedTypes { private class PolicyInputStep extends DataFlow::SharedFlowStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { exists(PolicyCreation policy, string method | - pred = policy.getReturn().getMember(method).getParameter(0).getARhs() and + pred = policy.getReturn().getMember(method).getParameter(0).asSink() and succ = policy.getPolicyCallback(method).getParameter(0) ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/UriLibraries.qll b/javascript/ql/lib/semmle/javascript/frameworks/UriLibraries.qll index 6551ef469e0..492139b3641 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/UriLibraries.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/UriLibraries.qll @@ -190,7 +190,7 @@ module Querystringify { * Gets a data flow source node for member `name` of the querystringify library. */ DataFlow::SourceNode querystringifyMember(string name) { - result = querystringify().getMember(name).getAnImmediateUse() + result = querystringify().getMember(name).asSource() } /** Gets an API node referring to the `querystringify` module. */ diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll index d32db710fa8..95a372025e2 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll @@ -9,9 +9,7 @@ module Vue { private class GlobalVueEntryPoint extends API::EntryPoint { GlobalVueEntryPoint() { this = "VueEntryPoint" } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("Vue") } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("Vue") } } /** @@ -22,9 +20,7 @@ module Vue { private class VueExportEntryPoint extends API::EntryPoint { VueExportEntryPoint() { this = "VueExportEntryPoint" } - override DataFlow::SourceNode getAUse() { none() } - - override DataFlow::Node getARhs() { + override DataFlow::Node getASink() { result = any(SingleFileComponent c).getModule().getDefaultOrBulkExport() } } @@ -41,7 +37,7 @@ module Vue { /** * Gets a reference to the 'Vue' object. */ - DataFlow::SourceNode vue() { result = vueLibrary().getAnImmediateUse() } + DataFlow::SourceNode vue() { result = vueLibrary().asSource() } /** Gets an API node referring to a component or `Vue`. */ private API::Node component() { @@ -176,8 +172,8 @@ module Vue { /** Gets a component which is extended by this one. */ Component getABaseComponent() { - result.getComponentRef().getAUse() = - getOwnOptions().getMember(["extends", "mixins"]).getARhs() + result.getComponentRef().getAValueReachableFromSource() = + getOwnOptions().getMember(["extends", "mixins"]).asSink() } /** @@ -195,12 +191,12 @@ module Vue { } /** - * DEPRECATED. Use `getOwnOptions().getARhs()`. + * DEPRECATED. Use `getOwnOptions().getASink()`. * * Gets the options passed to the Vue object, such as the object literal `{...}` in `new Vue{{...})` * or the default export of a single-file component. */ - deprecated DataFlow::Node getOwnOptionsObject() { result = getOwnOptions().getARhs() } + deprecated DataFlow::Node getOwnOptionsObject() { result = getOwnOptions().asSink() } /** * Gets the class implementing this Vue component, if any. @@ -208,19 +204,19 @@ module Vue { * Specifically, this is a class annotated with `@Component` which flows to the options * object of this Vue component. */ - ClassComponent getAsClassComponent() { result = getOwnOptions().getAValueReachingRhs() } + ClassComponent getAsClassComponent() { result = getOwnOptions().getAValueReachingSink() } /** * Gets the node for option `name` for this component, not including * those from extended objects and mixins. */ - DataFlow::Node getOwnOption(string name) { result = getOwnOptions().getMember(name).getARhs() } + DataFlow::Node getOwnOption(string name) { result = getOwnOptions().getMember(name).asSink() } /** * Gets the node for option `name` for this component, including those from * extended objects and mixins. */ - DataFlow::Node getOption(string name) { result = getOptions().getMember(name).getARhs() } + DataFlow::Node getOption(string name) { result = getOptions().getMember(name).asSink() } /** * Gets a source node flowing into the option `name` of this component, including those from @@ -228,7 +224,7 @@ module Vue { */ pragma[nomagic] DataFlow::SourceNode getOptionSource(string name) { - result = getOptions().getMember(name).getAValueReachingRhs() + result = getOptions().getMember(name).getAValueReachingSink() } /** @@ -289,7 +285,7 @@ module Vue { DataFlow::FunctionNode getWatchHandler(string propName) { exists(API::Node propWatch | propWatch = getOptions().getMember("watch").getMember(propName) and - result = [propWatch, propWatch.getMember("handler")].getAValueReachingRhs() + result = [propWatch, propWatch.getMember("handler")].getAValueReachingSink() ) } @@ -322,16 +318,16 @@ module Vue { * Gets a node for a function that will be invoked with `this` bound to this component. */ DataFlow::FunctionNode getABoundFunction() { - result = getOptions().getAMember+().getAValueReachingRhs() + result = getOptions().getAMember+().getAValueReachingSink() or result = getAsClassComponent().getAnInstanceMember() } /** Gets an API node referring to an instance of this component. */ - API::Node getInstance() { result.getAnImmediateUse() = getABoundFunction().getReceiver() } + API::Node getInstance() { result.asSource() = getABoundFunction().getReceiver() } /** Gets a data flow node referring to an instance of this component. */ - DataFlow::SourceNode getAnInstanceRef() { result = getInstance().getAnImmediateUse() } + DataFlow::SourceNode getAnInstanceRef() { result = getInstance().asSource() } pragma[noinline] private DataFlow::PropWrite getAPropertyValueWrite(string name) { @@ -484,14 +480,12 @@ module Vue { private class VueFileImportEntryPoint extends API::EntryPoint { VueFileImportEntryPoint() { this = "VueFileImportEntryPoint" } - override DataFlow::SourceNode getAUse() { + override DataFlow::SourceNode getASource() { exists(Import imprt | imprt.getImportedPath().resolve() instanceof VueFile and result = imprt.getImportedModuleNode() ) } - - override DataFlow::Node getARhs() { none() } } /** @@ -533,13 +527,13 @@ module Vue { // of the .vue file. exists(Import imprt | imprt.getImportedPath().resolve() = file and - result.getAnImmediateUse() = imprt.getImportedModuleNode() + result.asSource() = imprt.getImportedModuleNode() ) } override API::Node getOwnOptions() { // Use the entry point generated by `VueExportEntryPoint` - result.getARhs() = getModule().getDefaultOrBulkExport() + result.asSink() = getModule().getDefaultOrBulkExport() } override string toString() { result = file.toString() } @@ -695,7 +689,7 @@ module Vue { t.start() and ( exists(API::Node router | router = API::moduleImport("vue-router") | - result = router.getInstance().getMember("currentRoute").getAnImmediateUse() + result = router.getInstance().getMember("currentRoute").asSource() or result = router @@ -703,17 +697,12 @@ module Vue { .getMember(["beforeEach", "beforeResolve", "afterEach"]) .getParameter(0) .getParameter([0, 1]) - .getAnImmediateUse() + .asSource() or - result = - router - .getParameter(0) - .getMember("scrollBehavior") - .getParameter([0, 1]) - .getAnImmediateUse() + result = router.getParameter(0).getMember("scrollBehavior").getParameter([0, 1]).asSource() ) or - result = routeConfig().getMember("beforeEnter").getParameter([0, 1]).getAnImmediateUse() + result = routeConfig().getMember("beforeEnter").getParameter([0, 1]).asSource() or exists(Component c | result = c.getABoundFunction().getAFunctionValue().getReceiver().getAPropertyRead("$route") diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Vuex.qll b/javascript/ql/lib/semmle/javascript/frameworks/Vuex.qll index 8d062a447aa..71132fb531d 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Vuex.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Vuex.qll @@ -75,7 +75,7 @@ module Vuex { or exists(API::CallNode call | call = vuex().getMember("createNamespacedHelpers").getACall() and - namespace = call.getParameter(0).getAValueReachingRhs().getStringValue() + "/" and + namespace = call.getParameter(0).getAValueReachingSink().getStringValue() + "/" and this = call.getReturn().getMember(helperName).getACall() ) ) @@ -88,7 +88,8 @@ module Vuex { pragma[noinline] string getNamespace() { getNumArgument() = 2 and - result = appendToNamespace(namespace, getParameter(0).getAValueReachingRhs().getStringValue()) + result = + appendToNamespace(namespace, getParameter(0).getAValueReachingSink().getStringValue()) or getNumArgument() = 1 and result = namespace @@ -99,28 +100,28 @@ module Vuex { */ predicate hasMapping(string localName, string storeName) { // mapGetters('foo') - getLastParameter().getAValueReachingRhs().getStringValue() = localName and + getLastParameter().getAValueReachingSink().getStringValue() = localName and storeName = getNamespace() + localName or // mapGetters(['foo', 'bar']) - getLastParameter().getUnknownMember().getAValueReachingRhs().getStringValue() = localName and + getLastParameter().getUnknownMember().getAValueReachingSink().getStringValue() = localName and storeName = getNamespace() + localName or // mapGetters({foo: 'bar'}) storeName = getNamespace() + - getLastParameter().getMember(localName).getAValueReachingRhs().getStringValue() and + getLastParameter().getMember(localName).getAValueReachingSink().getStringValue() and localName != "*" // ignore special API graph member named "*" } /** Gets the Vue component in which the generated functions are installed. */ Vue::Component getVueComponent() { exists(DataFlow::ObjectLiteralNode obj | - obj.getASpreadProperty() = getReturn().getAUse() and - result.getOwnOptions().getAMember().getARhs() = obj + obj.getASpreadProperty() = getReturn().getAValueReachableFromSource() and + result.getOwnOptions().getAMember().asSink() = obj ) or - result.getOwnOptions().getAMember().getARhs() = this + result.getOwnOptions().getAMember().asSink() = this } } @@ -146,7 +147,7 @@ module Vuex { /** Gets a value that is returned by a getter registered with the given name. */ private DataFlow::Node getterPred(string name) { exists(string prefix, string prop | - result = storeConfigObject(prefix).getMember("getters").getMember(prop).getReturn().getARhs() and + result = storeConfigObject(prefix).getMember("getters").getMember(prop).getReturn().asSink() and name = prefix + prop ) } @@ -154,12 +155,12 @@ module Vuex { /** Gets a property access that may receive the produced by a getter of the given name. */ private DataFlow::Node getterSucc(string name) { exists(string prefix, string prop | - result = storeRef(prefix).getMember("getters").getMember(prop).getAnImmediateUse() and + result = storeRef(prefix).getMember("getters").getMember(prop).asSource() and prop != "*" and name = prefix + prop ) or - result = getAMappedAccess("mapGetters", name).getAnImmediateUse() + result = getAMappedAccess("mapGetters", name).asSource() } /** Holds if `pred -> succ` is a step from a getter function to a relevant property access. */ @@ -212,19 +213,19 @@ module Vuex { commitCall = commitLikeFunctionRef(kind, prefix).getACall() | // commit('name', payload) - name = prefix + commitCall.getParameter(0).getAValueReachingRhs().getStringValue() and + name = prefix + commitCall.getParameter(0).getAValueReachingSink().getStringValue() and result = commitCall.getArgument(1) or // commit({type: 'name', ......}) name = prefix + - commitCall.getParameter(0).getMember("type").getAValueReachingRhs().getStringValue() and + commitCall.getParameter(0).getMember("type").getAValueReachingSink().getStringValue() and result = commitCall.getArgument(0) ) or // this.name(payload) // methods: {...mapMutations(['name'])} } - result = getAMappedAccess(getMapHelperForCommitKind(kind), name).getParameter(0).getARhs() + result = getAMappedAccess(getMapHelperForCommitKind(kind), name).getParameter(0).asSink() } /** Gets a node that refers the payload of a committed mutation with the given `name.` */ @@ -238,7 +239,7 @@ module Vuex { .getMember(getStorePropForCommitKind(kind)) .getMember(prop) .getParameter(1) - .getAnImmediateUse() and + .asSource() and prop != "*" and name = prefix + prop ) @@ -293,19 +294,17 @@ module Vuex { /** Gets a value that flows into the given access path of the state. */ DataFlow::Node stateMutationPred(string path) { - result = stateRefByAccessPath(path).getARhs() + result = stateRefByAccessPath(path).asSink() or exists(ExtendCall call, string base, string prop | - call.getDestinationOperand() = stateRefByAccessPath(base).getAUse() and + call.getDestinationOperand() = stateRefByAccessPath(base).getAValueReachableFromSource() and result = call.getASourceOperand().getALocalSource().getAPropertyWrite(prop).getRhs() and path = appendToNamespace(base, prop) ) } /** Gets a value that refers to the given access path of the state. */ - DataFlow::Node stateMutationSucc(string path) { - result = stateRefByAccessPath(path).getAnImmediateUse() - } + DataFlow::Node stateMutationSucc(string path) { result = stateRefByAccessPath(path).asSource() } /** Holds if `pred -> succ` is a step from state mutation to state access. */ predicate stateMutationStep(DataFlow::Node pred, DataFlow::Node succ) { @@ -325,7 +324,7 @@ module Vuex { exists(MapHelperCall call | call.getHelperName() = "mapState" and component = call.getVueComponent() and - result = call.getLastParameter().getMember(name).getReturn().getARhs() + result = call.getLastParameter().getMember(name).getReturn().asSink() ) } @@ -336,7 +335,7 @@ module Vuex { predicate mapStateHelperStep(DataFlow::Node pred, DataFlow::Node succ) { exists(Vue::Component component, string name | pred = mapStateHelperPred(component, name) and - succ = pragma[only_bind_out](component).getInstance().getMember(name).getAnImmediateUse() + succ = pragma[only_bind_out](component).getInstance().getMember(name).asSource() ) } @@ -378,7 +377,7 @@ module Vuex { /** Gets a package that can be considered an entry point for a Vuex app. */ private PackageJson entryPointPackage() { - result = getPackageJson(storeRef().getAnImmediateUse().getFile()) + result = getPackageJson(storeRef().asSource().getFile()) or // Any package that imports a store-creating package is considered a potential entry point. packageDependsOn(result, entryPointPackage()) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/XmlParsers.qll b/javascript/ql/lib/semmle/javascript/frameworks/XmlParsers.qll index 41cecb36941..355ee485246 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/XmlParsers.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/XmlParsers.qll @@ -100,7 +100,7 @@ module XML { } override DataFlow::Node getAResult() { - result = [doc(), element(), attr()].getAnImmediateUse() + result = [doc(), element(), attr()].asSource() or result = element().getMember(["name", "text"]).getACall() or @@ -282,11 +282,7 @@ module XML { override DataFlow::Node getAResult() { result = - parser - .getReturn() - .getMember(any(string s | s.matches("on%"))) - .getAParameter() - .getAnImmediateUse() + parser.getReturn().getMember(any(string s | s.matches("on%"))).getAParameter().asSource() } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll index f8b2118a55f..bbed827c3e5 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll @@ -26,7 +26,7 @@ import Shared::ModelOutput as ModelOutput * A remote flow source originating from a CSV source row. */ private class RemoteFlowSourceFromCsv extends RemoteFlowSource { - RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() } + RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() } override string getSourceType() { result = "Remote flow" } } @@ -37,8 +37,8 @@ private class RemoteFlowSourceFromCsv extends RemoteFlowSource { private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) { exists(API::Node predNode, API::Node succNode | Specific::summaryStep(predNode, succNode, kind) and - pred = predNode.getARhs() and - succ = succNode.getAnImmediateUse() + pred = predNode.asSink() and + succ = succNode.asSource() ) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll index 127d9ca5122..69563a3eab4 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll @@ -299,7 +299,7 @@ private class AccessPathRange extends AccessPath::Range { bindingset[token] API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) { // API graphs use the same label for arguments and parameters. An edge originating from a - // use-node represents be an argument, and an edge originating from a def-node represents a parameter. + // use-node represents an argument, and an edge originating from a def-node represents a parameter. // We just map both to the same thing. token.getName() = ["Argument", "Parameter"] and result = node.getParameter(AccessPath::parseIntUnbounded(token.getAnArgument())) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll index a5e366a671f..861a44a2cfc 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -61,9 +61,7 @@ private class GlobalApiEntryPoint extends API::EntryPoint { this = "GlobalApiEntryPoint:" + global } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef(global) } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef(global) } /** Gets the name of the global variable. */ string getGlobal() { result = global } @@ -151,7 +149,7 @@ API::Node getExtraSuccessorFromInvoke(API::InvokeNode node, AccessPathToken toke or token.getName() = "Argument" and token.getAnArgument() = "this" and - result.getARhs() = node.(DataFlow::CallNode).getReceiver() + result.asSink() = node.(DataFlow::CallNode).getReceiver() } /** diff --git a/javascript/ql/lib/semmle/javascript/heuristics/AdditionalSources.qll b/javascript/ql/lib/semmle/javascript/heuristics/AdditionalSources.qll index 35b1ac83e2d..40c86ed4855 100644 --- a/javascript/ql/lib/semmle/javascript/heuristics/AdditionalSources.qll +++ b/javascript/ql/lib/semmle/javascript/heuristics/AdditionalSources.qll @@ -58,7 +58,7 @@ class RemoteServerResponse extends HeuristicSource, RemoteFlowSource { */ private class RemoteFlowSourceFromDBAccess extends RemoteFlowSource, HeuristicSource { RemoteFlowSourceFromDBAccess() { - this = ModelOutput::getASourceNode("database-access-result").getAUse() or + this = ModelOutput::getASourceNode("database-access-result").getAValueReachableFromSource() or exists(DatabaseAccess dba | this = dba.getAResult()) } diff --git a/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll b/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll index a53710a9e43..97193953c86 100644 --- a/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll +++ b/javascript/ql/lib/semmle/javascript/internal/CachedStages.qll @@ -176,6 +176,8 @@ module Stages { exists(DataFlow::moduleImport(_)) or exists(any(ReExportDeclaration d).getReExportedModule()) + or + exists(any(Module m).getABulkExportedNode()) } } @@ -276,6 +278,9 @@ module Stages { .getInstance() .getReceiver() .getPromisedError() + .getADecoratedClass() + .getADecoratedMember() + .getADecoratedParameter() ) } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll index 93c9fa63a59..b23f52d7c22 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll @@ -49,7 +49,7 @@ module DomBasedXss { or // A construction of a JSDOM object (server side DOM), where scripts are allowed. exists(DataFlow::NewNode instance | - instance = API::moduleImport("jsdom").getMember("JSDOM").getInstance().getAnImmediateUse() and + instance = API::moduleImport("jsdom").getMember("JSDOM").getInstance().asSource() and this = instance.getArgument(0) and instance.getOptionArgument(1, "runScripts").mayHaveStringValue("dangerously") ) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ExceptionXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ExceptionXssCustomizations.qll index 30ae50c2382..2418962ef65 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ExceptionXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ExceptionXssCustomizations.qll @@ -61,7 +61,7 @@ module ExceptionXss { */ private class JsonSchemaValidationError extends Source { JsonSchemaValidationError() { - this = any(JsonSchema::Ajv::Instance i).getAValidationError().getAnImmediateUse() + this = any(JsonSchema::Ajv::Instance i).getAValidationError().asSource() or this = any(JsonSchema::Joi::JoiValidationErrorRead r).getAValidationResultAccess(_) } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll index a061fd5bc6f..fb663a755ed 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ExternalAPIUsedWithUntrustedDataCustomizations.qll @@ -48,7 +48,7 @@ module ExternalApiUsedWithUntrustedData { } /** Holds if `node` corresponds to a deep object argument. */ - private predicate isDeepObjectSink(API::Node node) { node.getARhs() instanceof DeepObjectSink } + private predicate isDeepObjectSink(API::Node node) { node.asSink() instanceof DeepObjectSink } /** * A sanitizer for data flowing to an external API. @@ -165,9 +165,9 @@ module ExternalApiUsedWithUntrustedData { not param = base.getReceiver() | result = param and - name = param.getAnImmediateUse().asExpr().(Parameter).getName() + name = param.asSource().asExpr().(Parameter).getName() or - param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and + param.asSource().asExpr() instanceof DestructuringPattern and result = param.getMember(name) ) } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll index a410bda46b5..108ce5d8e62 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll @@ -74,7 +74,7 @@ module IndirectCommandInjection { ].getMember("parse").getACall() or // `require('commander').myCmdArgumentName` - this = commander().getAMember().getAnImmediateUse() + this = commander().getAMember().asSource() or // `require('commander').opt()` => `{a: ..., b: ...}` this = commander().getMember("opts").getACall() diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll index fc30c91018a..f1a27697725 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll @@ -152,9 +152,7 @@ abstract class RateLimitingMiddleware extends DataFlow::SourceNode { * A rate limiter constructed using the `express-rate-limit` package. */ class ExpressRateLimit extends RateLimitingMiddleware { - ExpressRateLimit() { - this = API::moduleImport("express-rate-limit").getReturn().getAnImmediateUse() - } + ExpressRateLimit() { this = API::moduleImport("express-rate-limit").getReturn().asSource() } } /** @@ -162,7 +160,7 @@ class ExpressRateLimit extends RateLimitingMiddleware { */ class BruteForceRateLimit extends RateLimitingMiddleware { BruteForceRateLimit() { - this = API::moduleImport("express-brute").getInstance().getMember("prevent").getAnImmediateUse() + this = API::moduleImport("express-brute").getInstance().getMember("prevent").asSource() } } @@ -174,7 +172,7 @@ class BruteForceRateLimit extends RateLimitingMiddleware { */ class RouteHandlerLimitedByExpressLimiter extends RateLimitingMiddleware { RouteHandlerLimitedByExpressLimiter() { - this = API::moduleImport("express-limiter").getReturn().getReturn().getAnImmediateUse() + this = API::moduleImport("express-limiter").getReturn().getReturn().asSource() } override Routing::Node getRoutingNode() { @@ -211,7 +209,7 @@ class RateLimiterFlexibleRateLimiter extends DataFlow::FunctionNode { rateLimiterClass = API::moduleImport("rate-limiter-flexible").getMember(rateLimiterClassName) and rateLimiterConsume = rateLimiterClass.getInstance().getMember("consume") and request.getParameter() = getRouteHandlerParameter(this.getFunction(), "request") and - request.getAPropertyRead().flowsTo(rateLimiterConsume.getAParameter().getARhs()) + request.getAPropertyRead().flowsTo(rateLimiterConsume.getAParameter().asSink()) ) } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll index ff026f3a3a4..bb60b5bc23e 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -164,9 +164,7 @@ private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint { string getName() { result = name } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef(name) } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef(name) } } /** @@ -175,7 +173,7 @@ private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint { private class ExternalRemoteFlowSource extends RemoteFlowSource { RemoteFlowSourceAccessPath ap; - ExternalRemoteFlowSource() { Stages::Taint::ref() and this = ap.resolve().getAnImmediateUse() } + ExternalRemoteFlowSource() { Stages::Taint::ref() and this = ap.resolve().asSource() } override string getSourceType() { result = ap.getSourceType() } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll index e3bf02362a4..42ffe2ea3c3 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll @@ -51,7 +51,7 @@ module SqlInjection { this = any(LdapJS::ClientCall call).getArgument(0) or // A search options object, which contains a filter and a baseDN. - this = any(LdapJS::SearchOptions opt).getARhs() + this = any(LdapJS::SearchOptions opt).asSink() or // A call to "parseDN", which parses a DN from a string. this = LdapJS::ldapjs().getMember("parseDN").getACall().getArgument(0) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index c094d82163c..03b1211326a 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -681,7 +681,7 @@ module TaintedPath { .getMember(["pdf", "screenshot"]) .getParameter(0) .getMember("path") - .getARhs() + .asSink() } } @@ -702,7 +702,7 @@ module TaintedPath { .getACall() .getParameter(1) .getMember("config") - .getARhs() + .asSink() } } @@ -716,7 +716,7 @@ module TaintedPath { .getMember(["readPackageAsync", "readPackageSync"]) .getParameter(0) .getMember("cwd") - .getARhs() + .asSink() } } @@ -726,8 +726,8 @@ module TaintedPath { private class ShellCwdSink extends TaintedPath::Sink { ShellCwdSink() { exists(SystemCommandExecution sys, API::Node opts | - opts.getARhs() = sys.getOptionsArg() and // assuming that an API::Node exists here. - this = opts.getMember("cwd").getARhs() + opts.asSink() = sys.getOptionsArg() and // assuming that an API::Node exists here. + this = opts.getMember("cwd").asSink() ) } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingQuery.qll index da6b698d97f..8e24b3e36d8 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TypeConfusionThroughParameterTamperingQuery.qll @@ -27,4 +27,30 @@ class Configuration extends DataFlow::Configuration { } override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier } + + override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) { + guard instanceof TypeOfTestBarrier or + guard instanceof IsArrayBarrier + } +} + +private class TypeOfTestBarrier extends DataFlow::BarrierGuardNode, DataFlow::ValueNode { + override EqualityTest astNode; + + TypeOfTestBarrier() { TaintTracking::isTypeofGuard(astNode, _, _) } + + override predicate blocks(boolean outcome, Expr e) { + if TaintTracking::isTypeofGuard(astNode, e, ["string", "object"]) + then outcome = [true, false] // separation between string/array removes type confusion in both branches + else outcome = astNode.getPolarity() // block flow to branch where value is neither string nor array + } +} + +private class IsArrayBarrier extends DataFlow::BarrierGuardNode, DataFlow::CallNode { + IsArrayBarrier() { this = DataFlow::globalVarRef("Array").getAMemberCall("isArray").getACall() } + + override predicate blocks(boolean outcome, Expr e) { + e = getArgument(0).asExpr() and + outcome = [true, false] // separation between string/array removes type confusion in both branches + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll index b869b028902..3f0f569eff6 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll @@ -208,8 +208,7 @@ module XssThroughDom { exists(API::Node useForm | useForm = API::moduleImport("react-hook-form").getMember("useForm").getReturn() | - this = - useForm.getMember("handleSubmit").getParameter(0).getParameter(0).getAnImmediateUse() + this = useForm.getMember("handleSubmit").getParameter(0).getParameter(0).asSource() or this = useForm.getMember("getValues").getACall() ) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ZipSlipCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ZipSlipCustomizations.qll index 3c4c5f66b75..1cb58609d13 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ZipSlipCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ZipSlipCustomizations.qll @@ -103,7 +103,7 @@ module ZipSlip { class JSZipFilesSource extends Source instanceof DynamicPropertyAccess::EnumeratedPropName { JSZipFilesSource() { super.getSourceObject() = - API::moduleImport("jszip").getInstance().getMember("files").getAnImmediateUse() + API::moduleImport("jszip").getInstance().getMember("files").asSource() } } @@ -116,7 +116,7 @@ module ZipSlip { .getMember(["forEach", "filter"]) .getParameter(0) .getParameter(0) - .getAnImmediateUse() + .asSource() } } diff --git a/javascript/ql/src/Declarations/UnusedProperty.ql b/javascript/ql/src/Declarations/UnusedProperty.ql index 19d43a09db2..1a8f9ee291f 100644 --- a/javascript/ql/src/Declarations/UnusedProperty.ql +++ b/javascript/ql/src/Declarations/UnusedProperty.ql @@ -27,6 +27,8 @@ predicate hasUnknownPropertyRead(LocalObject obj) { or exists(obj.getAPropertyRead("hasOwnProperty")) or + obj.flowsTo(DataFlow::globalVarRef("Object").getAMemberCall("hasOwn").getArgument(0)) + or exists(obj.getAPropertyRead("propertyIsEnumerable")) } diff --git a/javascript/ql/src/Performance/PolynomialReDoS.qhelp b/javascript/ql/src/Performance/PolynomialReDoS.qhelp index 78746df38ad..210a4a8a84b 100644 --- a/javascript/ql/src/Performance/PolynomialReDoS.qhelp +++ b/javascript/ql/src/Performance/PolynomialReDoS.qhelp @@ -71,7 +71,7 @@

- ^0\.\d+E?\d+$ // BAD + /^0\.\d+E?\d+$/.test(str) // BAD

diff --git a/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql b/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql index 42861fcb9be..7fd76a635ec 100644 --- a/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql +++ b/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql @@ -45,7 +45,7 @@ where or // the same thing, but with API-nodes if they happen to be available exists(API::Node tlsInvk | tlsInvk.getAnInvocation() = tlsInvocation() | - disable.getRhs() = tlsInvk.getAParameter().getMember("rejectUnauthorized").getARhs() + disable.getRhs() = tlsInvk.getAParameter().getMember("rejectUnauthorized").asSink() ) ) and disable.getRhs().(AnalyzedNode).getTheBooleanValue() = false diff --git a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql index 52617ce675d..6240615a6e7 100644 --- a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql +++ b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql @@ -143,7 +143,7 @@ API::CallNode passportAuthenticateCall() { */ API::CallNode nonSessionBasedAuthMiddleware() { result = passportAuthenticateCall() and - result.getParameter(1).getMember("session").getARhs().mayHaveBooleanValue(false) + result.getParameter(1).getMember("session").asSink().mayHaveBooleanValue(false) } /** diff --git a/javascript/ql/src/Security/CWE-915/PrototypePollutingFunction.ql b/javascript/ql/src/Security/CWE-915/PrototypePollutingFunction.ql index 2fb9cece66d..9e5b873f663 100644 --- a/javascript/ql/src/Security/CWE-915/PrototypePollutingFunction.ql +++ b/javascript/ql/src/Security/CWE-915/PrototypePollutingFunction.ql @@ -339,19 +339,16 @@ class AllowListEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNod * but the destination object generally doesn't. It is therefore only a sanitizer when * used on the destination object. */ -class HasOwnPropertyGuard extends DataFlow::BarrierGuardNode, CallNode { +class HasOwnPropertyGuard extends DataFlow::BarrierGuardNode instanceof HasOwnPropertyCall { HasOwnPropertyGuard() { - // Make sure we handle reflective calls since libraries love to do that. - getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() = "hasOwnProperty" and - exists(getReceiver()) and // Try to avoid `src.hasOwnProperty` by requiring that the receiver // does not locally have its properties enumerated. Typically there is no // reason to enumerate the properties of the destination object. - not arePropertiesEnumerated(getReceiver().getALocalSource()) + not arePropertiesEnumerated(super.getObject().getALocalSource()) } override predicate blocks(boolean outcome, Expr e) { - e = getArgument(0).asExpr() and outcome = true + e = super.getProperty().asExpr() and outcome = true } } diff --git a/javascript/ql/src/meta/ApiGraphs/ApiGraphRhsNodes.ql b/javascript/ql/src/meta/ApiGraphs/ApiGraphRhsNodes.ql index fcb98dc57af..4f07f9be7fe 100644 --- a/javascript/ql/src/meta/ApiGraphs/ApiGraphRhsNodes.ql +++ b/javascript/ql/src/meta/ApiGraphs/ApiGraphRhsNodes.ql @@ -12,4 +12,4 @@ import javascript import meta.MetaMetrics -select projectRoot(), count(any(API::Node nd).getARhs()) +select projectRoot(), count(any(API::Node nd).asSink()) diff --git a/javascript/ql/src/meta/ApiGraphs/ApiGraphUseNodes.ql b/javascript/ql/src/meta/ApiGraphs/ApiGraphUseNodes.ql index 446df101364..8f6629cc624 100644 --- a/javascript/ql/src/meta/ApiGraphs/ApiGraphUseNodes.ql +++ b/javascript/ql/src/meta/ApiGraphs/ApiGraphUseNodes.ql @@ -11,4 +11,4 @@ import javascript import meta.MetaMetrics -select projectRoot(), count(any(API::Node nd).getAUse()) +select projectRoot(), count(any(API::Node nd).getAValueReachableFromSource()) diff --git a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll index a81c0d03ae9..c65f2b7ad81 100644 --- a/javascript/ql/test/ApiGraphs/VerifyAssertions.qll +++ b/javascript/ql/test/ApiGraphs/VerifyAssertions.qll @@ -21,10 +21,10 @@ import javascript private DataFlow::Node getNode(API::Node nd, string kind) { kind = "def" and - result = nd.getARhs() + result = nd.asSink() or kind = "use" and - result = nd.getAUse() + result = nd.getAValueReachableFromSource() } private string getLoc(DataFlow::Node nd) { diff --git a/javascript/ql/test/ApiGraphs/call-nodes/test.ql b/javascript/ql/test/ApiGraphs/call-nodes/test.ql index cbea83fa4be..8f0ea4bfc8b 100644 --- a/javascript/ql/test/ApiGraphs/call-nodes/test.ql +++ b/javascript/ql/test/ApiGraphs/call-nodes/test.ql @@ -3,9 +3,9 @@ import javascript class FooCall extends API::CallNode { FooCall() { this = API::moduleImport("mylibrary").getMember("foo").getACall() } - DataFlow::Node getFirst() { result = getParameter(0).getMember("value").getARhs() } + DataFlow::Node getFirst() { result = getParameter(0).getMember("value").asSink() } - DataFlow::Node getSecond() { result = getParameter(1).getMember("value").getARhs() } + DataFlow::Node getSecond() { result = getParameter(1).getMember("value").asSink() } } query predicate values(FooCall call, int first, int second) { diff --git a/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql b/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql index fb8a943f2ca..3502c0ea556 100644 --- a/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql +++ b/javascript/ql/test/ApiGraphs/custom-entry-point/VerifyAssertions.ql @@ -1,9 +1,7 @@ class CustomEntryPoint extends API::EntryPoint { CustomEntryPoint() { this = "CustomEntryPoint" } - override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("CustomEntryPoint") } - - override DataFlow::Node getARhs() { none() } + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("CustomEntryPoint") } } import ApiGraphs.VerifyAssertions diff --git a/javascript/ql/test/ApiGraphs/typed/NodeOfType.ql b/javascript/ql/test/ApiGraphs/typed/NodeOfType.ql index 582385c802a..10c5133f9a8 100644 --- a/javascript/ql/test/ApiGraphs/typed/NodeOfType.ql +++ b/javascript/ql/test/ApiGraphs/typed/NodeOfType.ql @@ -1,4 +1,4 @@ import javascript from string mod, string tp -select mod, tp, API::Node::ofType(mod, tp).getAnImmediateUse() +select mod, tp, API::Node::ofType(mod, tp).asSource() diff --git a/javascript/ql/test/library-tests/Arrays/DataFlow.expected b/javascript/ql/test/library-tests/Arrays/DataFlow.expected index fd98b66d4db..2f5179075cf 100644 --- a/javascript/ql/test/library-tests/Arrays/DataFlow.expected +++ b/javascript/ql/test/library-tests/Arrays/DataFlow.expected @@ -11,6 +11,7 @@ | arrays.js:2:16:2:23 | "source" | arrays.js:74:8:74:29 | arr.fin ... llback) | | arrays.js:2:16:2:23 | "source" | arrays.js:77:8:77:35 | arrayFi ... llback) | | arrays.js:2:16:2:23 | "source" | arrays.js:81:10:81:10 | x | +| arrays.js:2:16:2:23 | "source" | arrays.js:84:8:84:17 | arr.at(-1) | | arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e | | arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() | | arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() | diff --git a/javascript/ql/test/library-tests/Arrays/arrays.js b/javascript/ql/test/library-tests/Arrays/arrays.js index 9b445760f47..2dfb203cd54 100644 --- a/javascript/ql/test/library-tests/Arrays/arrays.js +++ b/javascript/ql/test/library-tests/Arrays/arrays.js @@ -80,4 +80,6 @@ for (const x of uniq(arr)) { sink(x); // NOT OK } + + sink(arr.at(-1)); // NOT OK }); diff --git a/javascript/ql/test/library-tests/Arrays/printAst.expected b/javascript/ql/test/library-tests/Arrays/printAst.expected index 8364838f8bd..c6e097c770e 100644 --- a/javascript/ql/test/library-tests/Arrays/printAst.expected +++ b/javascript/ql/test/library-tests/Arrays/printAst.expected @@ -1,9 +1,9 @@ nodes -| arrays.js:1:1:83:2 | [ParExpr] (functi ... } }) | semmle.label | [ParExpr] (functi ... } }) | -| arrays.js:1:1:83:3 | [ExprStmt] (functi ... } }); | semmle.label | [ExprStmt] (functi ... } }); | -| arrays.js:1:1:83:3 | [ExprStmt] (functi ... } }); | semmle.order | 1 | -| arrays.js:1:2:83:1 | [FunctionExpr] functio ... K } } | semmle.label | [FunctionExpr] functio ... K } } | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | semmle.label | [BlockStmt] { let ... K } } | +| arrays.js:1:1:85:2 | [ParExpr] (functi ... T OK }) | semmle.label | [ParExpr] (functi ... T OK }) | +| arrays.js:1:1:85:3 | [ExprStmt] (functi ... OK }); | semmle.label | [ExprStmt] (functi ... OK }); | +| arrays.js:1:1:85:3 | [ExprStmt] (functi ... OK }); | semmle.order | 1 | +| arrays.js:1:2:85:1 | [FunctionExpr] functio ... OT OK } | semmle.label | [FunctionExpr] functio ... OT OK } | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | semmle.label | [BlockStmt] { let ... OT OK } | | arrays.js:2:3:2:24 | [DeclStmt] let source = ... | semmle.label | [DeclStmt] let source = ... | | arrays.js:2:7:2:12 | [VarDecl] source | semmle.label | [VarDecl] source | | arrays.js:2:7:2:23 | [VariableDeclarator] source = "source" | semmle.label | [VariableDeclarator] source = "source" | @@ -339,6 +339,17 @@ nodes | arrays.js:81:5:81:11 | [CallExpr] sink(x) | semmle.label | [CallExpr] sink(x) | | arrays.js:81:5:81:12 | [ExprStmt] sink(x); | semmle.label | [ExprStmt] sink(x); | | arrays.js:81:10:81:10 | [VarRef] x | semmle.label | [VarRef] x | +| arrays.js:84:3:84:6 | [VarRef] sink | semmle.label | [VarRef] sink | +| arrays.js:84:3:84:18 | [CallExpr] sink(arr.at(-1)) | semmle.label | [CallExpr] sink(arr.at(-1)) | +| arrays.js:84:3:84:19 | [ExprStmt] sink(arr.at(-1)); | semmle.label | [ExprStmt] sink(arr.at(-1)); | +| arrays.js:84:8:84:10 | [VarRef] arr | semmle.label | [VarRef] arr | +| arrays.js:84:8:84:13 | [DotExpr] arr.at | semmle.label | [DotExpr] arr.at | +| arrays.js:84:8:84:17 | [MethodCallExpr] arr.at(-1) | semmle.label | [MethodCallExpr] arr.at(-1) | +| arrays.js:84:12:84:13 | [Label] at | semmle.label | [Label] at | +| arrays.js:84:15:84:16 | [UnaryExpr] -1 | semmle.label | [UnaryExpr] -1 | +| arrays.js:84:16:84:16 | [Literal] 1 | semmle.label | [Literal] 1 | +| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | +| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | | file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | | file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | | file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | @@ -386,88 +397,90 @@ nodes | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | edges -| arrays.js:1:1:83:2 | [ParExpr] (functi ... } }) | arrays.js:1:2:83:1 | [FunctionExpr] functio ... K } } | semmle.label | 1 | -| arrays.js:1:1:83:2 | [ParExpr] (functi ... } }) | arrays.js:1:2:83:1 | [FunctionExpr] functio ... K } } | semmle.order | 1 | -| arrays.js:1:1:83:3 | [ExprStmt] (functi ... } }); | arrays.js:1:1:83:2 | [ParExpr] (functi ... } }) | semmle.label | 1 | -| arrays.js:1:1:83:3 | [ExprStmt] (functi ... } }); | arrays.js:1:1:83:2 | [ParExpr] (functi ... } }) | semmle.order | 1 | -| arrays.js:1:2:83:1 | [FunctionExpr] functio ... K } } | arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | semmle.label | 5 | -| arrays.js:1:2:83:1 | [FunctionExpr] functio ... K } } | arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | semmle.order | 5 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:2:3:2:24 | [DeclStmt] let source = ... | semmle.label | 1 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:2:3:2:24 | [DeclStmt] let source = ... | semmle.order | 1 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:4:3:4:28 | [DeclStmt] var obj = ... | semmle.label | 2 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:4:3:4:28 | [DeclStmt] var obj = ... | semmle.order | 2 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:5:3:5:16 | [ExprStmt] sink(obj.foo); | semmle.label | 3 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:5:3:5:16 | [ExprStmt] sink(obj.foo); | semmle.order | 3 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:7:3:7:15 | [DeclStmt] var arr = ... | semmle.label | 4 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:7:3:7:15 | [DeclStmt] var arr = ... | semmle.order | 4 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:8:3:8:19 | [ExprStmt] arr.push(source); | semmle.label | 5 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:8:3:8:19 | [ExprStmt] arr.push(source); | semmle.order | 5 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:10:3:12:3 | [ForStmt] for (va ... OK } | semmle.label | 6 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:10:3:12:3 | [ForStmt] for (va ... OK } | semmle.order | 6 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:15:3:15:30 | [ExprStmt] arr.for ... nk(e)); | semmle.label | 7 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:15:3:15:30 | [ExprStmt] arr.for ... nk(e)); | semmle.order | 7 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:16:3:16:26 | [ExprStmt] arr.map ... nk(e)); | semmle.label | 8 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:16:3:16:26 | [ExprStmt] arr.map ... nk(e)); | semmle.order | 8 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:18:3:18:53 | [ExprStmt] [1, 2, ... nk(e)); | semmle.label | 9 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:18:3:18:53 | [ExprStmt] [1, 2, ... nk(e)); | semmle.order | 9 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:20:3:20:18 | [ExprStmt] sink(arr.pop()); | semmle.label | 10 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:20:3:20:18 | [ExprStmt] sink(arr.pop()); | semmle.order | 10 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:22:3:22:24 | [DeclStmt] var arr2 = ... | semmle.label | 11 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:22:3:22:24 | [DeclStmt] var arr2 = ... | semmle.order | 11 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:23:3:23:19 | [ExprStmt] sink(arr2.pop()); | semmle.label | 12 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:23:3:23:19 | [ExprStmt] sink(arr2.pop()); | semmle.order | 12 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:25:3:25:24 | [DeclStmt] var arr3 = ... | semmle.label | 13 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:25:3:25:24 | [DeclStmt] var arr3 = ... | semmle.order | 13 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:26:3:26:19 | [ExprStmt] sink(arr3.pop()); | semmle.label | 14 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:26:3:26:19 | [ExprStmt] sink(arr3.pop()); | semmle.order | 14 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:28:3:28:16 | [DeclStmt] var arr4 = ... | semmle.label | 15 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:28:3:28:16 | [DeclStmt] var arr4 = ... | semmle.order | 15 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:29:3:29:30 | [ExprStmt] arr4.sp ... urce"); | semmle.label | 16 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:29:3:29:30 | [ExprStmt] arr4.sp ... urce"); | semmle.order | 16 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:30:3:30:19 | [ExprStmt] sink(arr4.pop()); | semmle.label | 17 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:30:3:30:19 | [ExprStmt] sink(arr4.pop()); | semmle.order | 17 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:32:3:32:29 | [DeclStmt] var arr5 = ... | semmle.label | 18 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:32:3:32:29 | [DeclStmt] var arr5 = ... | semmle.order | 18 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:33:3:33:19 | [ExprStmt] sink(arr5.pop()); | semmle.label | 19 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:33:3:33:19 | [ExprStmt] sink(arr5.pop()); | semmle.order | 19 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:35:3:35:28 | [ExprStmt] sink(ar ... pop()); | semmle.label | 20 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:35:3:35:28 | [ExprStmt] sink(ar ... pop()); | semmle.order | 20 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:37:3:37:16 | [DeclStmt] var arr6 = ... | semmle.label | 21 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:37:3:37:16 | [DeclStmt] var arr6 = ... | semmle.order | 21 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:38:3:40:3 | [ForStmt] for (va ... i]; } | semmle.label | 22 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:38:3:40:3 | [ForStmt] for (va ... i]; } | semmle.order | 22 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:41:3:41:19 | [ExprStmt] sink(arr6.pop()); | semmle.label | 23 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:41:3:41:19 | [ExprStmt] sink(arr6.pop()); | semmle.order | 23 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:44:3:47:5 | [ExprStmt] ["sourc ... . }); | semmle.label | 24 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:44:3:47:5 | [ExprStmt] ["sourc ... . }); | semmle.order | 24 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:49:3:49:15 | [ExprStmt] sink(arr[0]); | semmle.label | 25 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:49:3:49:15 | [ExprStmt] sink(arr[0]); | semmle.order | 25 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:51:3:53:3 | [ForOfStmt] for (co ... OK } | semmle.label | 26 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:51:3:53:3 | [ForOfStmt] for (co ... OK } | semmle.order | 26 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:55:3:57:3 | [ForOfStmt] for (co ... OK } | semmle.label | 27 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:55:3:57:3 | [ForOfStmt] for (co ... OK } | semmle.order | 27 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:59:3:61:3 | [ForOfStmt] for (co ... OK } | semmle.label | 28 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:59:3:61:3 | [ForOfStmt] for (co ... OK } | semmle.order | 28 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:63:3:63:16 | [DeclStmt] var arr7 = ... | semmle.label | 29 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:63:3:63:16 | [DeclStmt] var arr7 = ... | semmle.order | 29 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:64:3:64:20 | [ExprStmt] arr7.push(...arr); | semmle.label | 30 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:64:3:64:20 | [ExprStmt] arr7.push(...arr); | semmle.order | 30 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:65:3:67:3 | [ForOfStmt] for (co ... OK } | semmle.label | 31 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:65:3:67:3 | [ForOfStmt] for (co ... OK } | semmle.order | 31 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:69:3:69:42 | [DeclStmt] const arrayFrom = ... | semmle.label | 32 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:69:3:69:42 | [DeclStmt] const arrayFrom = ... | semmle.order | 32 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:70:3:72:3 | [ForOfStmt] for (co ... OK } | semmle.label | 33 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:70:3:72:3 | [ForOfStmt] for (co ... OK } | semmle.order | 33 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:74:3:74:31 | [ExprStmt] sink(ar ... back)); | semmle.label | 34 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:74:3:74:31 | [ExprStmt] sink(ar ... back)); | semmle.order | 34 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:76:3:76:42 | [DeclStmt] const arrayFind = ... | semmle.label | 35 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:76:3:76:42 | [DeclStmt] const arrayFind = ... | semmle.order | 35 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:77:3:77:37 | [ExprStmt] sink(ar ... back)); | semmle.label | 36 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:77:3:77:37 | [ExprStmt] sink(ar ... back)); | semmle.order | 36 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:79:3:79:31 | [DeclStmt] const uniq = ... | semmle.label | 37 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:79:3:79:31 | [DeclStmt] const uniq = ... | semmle.order | 37 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:80:3:82:3 | [ForOfStmt] for (co ... OK } | semmle.label | 38 | -| arrays.js:1:14:83:1 | [BlockStmt] { let ... K } } | arrays.js:80:3:82:3 | [ForOfStmt] for (co ... OK } | semmle.order | 38 | +| arrays.js:1:1:85:2 | [ParExpr] (functi ... T OK }) | arrays.js:1:2:85:1 | [FunctionExpr] functio ... OT OK } | semmle.label | 1 | +| arrays.js:1:1:85:2 | [ParExpr] (functi ... T OK }) | arrays.js:1:2:85:1 | [FunctionExpr] functio ... OT OK } | semmle.order | 1 | +| arrays.js:1:1:85:3 | [ExprStmt] (functi ... OK }); | arrays.js:1:1:85:2 | [ParExpr] (functi ... T OK }) | semmle.label | 1 | +| arrays.js:1:1:85:3 | [ExprStmt] (functi ... OK }); | arrays.js:1:1:85:2 | [ParExpr] (functi ... T OK }) | semmle.order | 1 | +| arrays.js:1:2:85:1 | [FunctionExpr] functio ... OT OK } | arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | semmle.label | 5 | +| arrays.js:1:2:85:1 | [FunctionExpr] functio ... OT OK } | arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | semmle.order | 5 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:2:3:2:24 | [DeclStmt] let source = ... | semmle.label | 1 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:2:3:2:24 | [DeclStmt] let source = ... | semmle.order | 1 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:4:3:4:28 | [DeclStmt] var obj = ... | semmle.label | 2 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:4:3:4:28 | [DeclStmt] var obj = ... | semmle.order | 2 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:5:3:5:16 | [ExprStmt] sink(obj.foo); | semmle.label | 3 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:5:3:5:16 | [ExprStmt] sink(obj.foo); | semmle.order | 3 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:7:3:7:15 | [DeclStmt] var arr = ... | semmle.label | 4 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:7:3:7:15 | [DeclStmt] var arr = ... | semmle.order | 4 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:8:3:8:19 | [ExprStmt] arr.push(source); | semmle.label | 5 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:8:3:8:19 | [ExprStmt] arr.push(source); | semmle.order | 5 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:10:3:12:3 | [ForStmt] for (va ... OK } | semmle.label | 6 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:10:3:12:3 | [ForStmt] for (va ... OK } | semmle.order | 6 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:15:3:15:30 | [ExprStmt] arr.for ... nk(e)); | semmle.label | 7 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:15:3:15:30 | [ExprStmt] arr.for ... nk(e)); | semmle.order | 7 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:16:3:16:26 | [ExprStmt] arr.map ... nk(e)); | semmle.label | 8 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:16:3:16:26 | [ExprStmt] arr.map ... nk(e)); | semmle.order | 8 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:18:3:18:53 | [ExprStmt] [1, 2, ... nk(e)); | semmle.label | 9 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:18:3:18:53 | [ExprStmt] [1, 2, ... nk(e)); | semmle.order | 9 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:20:3:20:18 | [ExprStmt] sink(arr.pop()); | semmle.label | 10 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:20:3:20:18 | [ExprStmt] sink(arr.pop()); | semmle.order | 10 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:22:3:22:24 | [DeclStmt] var arr2 = ... | semmle.label | 11 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:22:3:22:24 | [DeclStmt] var arr2 = ... | semmle.order | 11 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:23:3:23:19 | [ExprStmt] sink(arr2.pop()); | semmle.label | 12 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:23:3:23:19 | [ExprStmt] sink(arr2.pop()); | semmle.order | 12 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:25:3:25:24 | [DeclStmt] var arr3 = ... | semmle.label | 13 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:25:3:25:24 | [DeclStmt] var arr3 = ... | semmle.order | 13 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:26:3:26:19 | [ExprStmt] sink(arr3.pop()); | semmle.label | 14 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:26:3:26:19 | [ExprStmt] sink(arr3.pop()); | semmle.order | 14 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:28:3:28:16 | [DeclStmt] var arr4 = ... | semmle.label | 15 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:28:3:28:16 | [DeclStmt] var arr4 = ... | semmle.order | 15 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:29:3:29:30 | [ExprStmt] arr4.sp ... urce"); | semmle.label | 16 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:29:3:29:30 | [ExprStmt] arr4.sp ... urce"); | semmle.order | 16 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:30:3:30:19 | [ExprStmt] sink(arr4.pop()); | semmle.label | 17 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:30:3:30:19 | [ExprStmt] sink(arr4.pop()); | semmle.order | 17 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:32:3:32:29 | [DeclStmt] var arr5 = ... | semmle.label | 18 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:32:3:32:29 | [DeclStmt] var arr5 = ... | semmle.order | 18 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:33:3:33:19 | [ExprStmt] sink(arr5.pop()); | semmle.label | 19 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:33:3:33:19 | [ExprStmt] sink(arr5.pop()); | semmle.order | 19 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:35:3:35:28 | [ExprStmt] sink(ar ... pop()); | semmle.label | 20 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:35:3:35:28 | [ExprStmt] sink(ar ... pop()); | semmle.order | 20 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:37:3:37:16 | [DeclStmt] var arr6 = ... | semmle.label | 21 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:37:3:37:16 | [DeclStmt] var arr6 = ... | semmle.order | 21 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:38:3:40:3 | [ForStmt] for (va ... i]; } | semmle.label | 22 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:38:3:40:3 | [ForStmt] for (va ... i]; } | semmle.order | 22 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:41:3:41:19 | [ExprStmt] sink(arr6.pop()); | semmle.label | 23 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:41:3:41:19 | [ExprStmt] sink(arr6.pop()); | semmle.order | 23 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:44:3:47:5 | [ExprStmt] ["sourc ... . }); | semmle.label | 24 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:44:3:47:5 | [ExprStmt] ["sourc ... . }); | semmle.order | 24 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:49:3:49:15 | [ExprStmt] sink(arr[0]); | semmle.label | 25 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:49:3:49:15 | [ExprStmt] sink(arr[0]); | semmle.order | 25 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:51:3:53:3 | [ForOfStmt] for (co ... OK } | semmle.label | 26 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:51:3:53:3 | [ForOfStmt] for (co ... OK } | semmle.order | 26 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:55:3:57:3 | [ForOfStmt] for (co ... OK } | semmle.label | 27 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:55:3:57:3 | [ForOfStmt] for (co ... OK } | semmle.order | 27 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:59:3:61:3 | [ForOfStmt] for (co ... OK } | semmle.label | 28 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:59:3:61:3 | [ForOfStmt] for (co ... OK } | semmle.order | 28 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:63:3:63:16 | [DeclStmt] var arr7 = ... | semmle.label | 29 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:63:3:63:16 | [DeclStmt] var arr7 = ... | semmle.order | 29 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:64:3:64:20 | [ExprStmt] arr7.push(...arr); | semmle.label | 30 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:64:3:64:20 | [ExprStmt] arr7.push(...arr); | semmle.order | 30 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:65:3:67:3 | [ForOfStmt] for (co ... OK } | semmle.label | 31 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:65:3:67:3 | [ForOfStmt] for (co ... OK } | semmle.order | 31 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:69:3:69:42 | [DeclStmt] const arrayFrom = ... | semmle.label | 32 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:69:3:69:42 | [DeclStmt] const arrayFrom = ... | semmle.order | 32 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:70:3:72:3 | [ForOfStmt] for (co ... OK } | semmle.label | 33 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:70:3:72:3 | [ForOfStmt] for (co ... OK } | semmle.order | 33 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:74:3:74:31 | [ExprStmt] sink(ar ... back)); | semmle.label | 34 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:74:3:74:31 | [ExprStmt] sink(ar ... back)); | semmle.order | 34 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:76:3:76:42 | [DeclStmt] const arrayFind = ... | semmle.label | 35 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:76:3:76:42 | [DeclStmt] const arrayFind = ... | semmle.order | 35 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:77:3:77:37 | [ExprStmt] sink(ar ... back)); | semmle.label | 36 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:77:3:77:37 | [ExprStmt] sink(ar ... back)); | semmle.order | 36 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:79:3:79:31 | [DeclStmt] const uniq = ... | semmle.label | 37 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:79:3:79:31 | [DeclStmt] const uniq = ... | semmle.order | 37 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:80:3:82:3 | [ForOfStmt] for (co ... OK } | semmle.label | 38 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:80:3:82:3 | [ForOfStmt] for (co ... OK } | semmle.order | 38 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:84:3:84:19 | [ExprStmt] sink(arr.at(-1)); | semmle.label | 39 | +| arrays.js:1:14:85:1 | [BlockStmt] { let ... OT OK } | arrays.js:84:3:84:19 | [ExprStmt] sink(arr.at(-1)); | semmle.order | 39 | | arrays.js:2:3:2:24 | [DeclStmt] let source = ... | arrays.js:2:7:2:23 | [VariableDeclarator] source = "source" | semmle.label | 1 | | arrays.js:2:3:2:24 | [DeclStmt] let source = ... | arrays.js:2:7:2:23 | [VariableDeclarator] source = "source" | semmle.order | 1 | | arrays.js:2:7:2:23 | [VariableDeclarator] source = "source" | arrays.js:2:7:2:12 | [VarDecl] source | semmle.label | 1 | @@ -1052,6 +1065,22 @@ edges | arrays.js:81:5:81:11 | [CallExpr] sink(x) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | | arrays.js:81:5:81:12 | [ExprStmt] sink(x); | arrays.js:81:5:81:11 | [CallExpr] sink(x) | semmle.label | 1 | | arrays.js:81:5:81:12 | [ExprStmt] sink(x); | arrays.js:81:5:81:11 | [CallExpr] sink(x) | semmle.order | 1 | +| arrays.js:84:3:84:18 | [CallExpr] sink(arr.at(-1)) | arrays.js:84:3:84:6 | [VarRef] sink | semmle.label | 0 | +| arrays.js:84:3:84:18 | [CallExpr] sink(arr.at(-1)) | arrays.js:84:3:84:6 | [VarRef] sink | semmle.order | 0 | +| arrays.js:84:3:84:18 | [CallExpr] sink(arr.at(-1)) | file://:0:0:0:0 | (Arguments) | semmle.label | 1 | +| arrays.js:84:3:84:18 | [CallExpr] sink(arr.at(-1)) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | +| arrays.js:84:3:84:19 | [ExprStmt] sink(arr.at(-1)); | arrays.js:84:3:84:18 | [CallExpr] sink(arr.at(-1)) | semmle.label | 1 | +| arrays.js:84:3:84:19 | [ExprStmt] sink(arr.at(-1)); | arrays.js:84:3:84:18 | [CallExpr] sink(arr.at(-1)) | semmle.order | 1 | +| arrays.js:84:8:84:13 | [DotExpr] arr.at | arrays.js:84:8:84:10 | [VarRef] arr | semmle.label | 1 | +| arrays.js:84:8:84:13 | [DotExpr] arr.at | arrays.js:84:8:84:10 | [VarRef] arr | semmle.order | 1 | +| arrays.js:84:8:84:13 | [DotExpr] arr.at | arrays.js:84:12:84:13 | [Label] at | semmle.label | 2 | +| arrays.js:84:8:84:13 | [DotExpr] arr.at | arrays.js:84:12:84:13 | [Label] at | semmle.order | 2 | +| arrays.js:84:8:84:17 | [MethodCallExpr] arr.at(-1) | arrays.js:84:8:84:13 | [DotExpr] arr.at | semmle.label | 0 | +| arrays.js:84:8:84:17 | [MethodCallExpr] arr.at(-1) | arrays.js:84:8:84:13 | [DotExpr] arr.at | semmle.order | 0 | +| arrays.js:84:8:84:17 | [MethodCallExpr] arr.at(-1) | file://:0:0:0:0 | (Arguments) | semmle.label | 1 | +| arrays.js:84:8:84:17 | [MethodCallExpr] arr.at(-1) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | +| arrays.js:84:15:84:16 | [UnaryExpr] -1 | arrays.js:84:16:84:16 | [Literal] 1 | semmle.label | 1 | +| arrays.js:84:15:84:16 | [UnaryExpr] -1 | arrays.js:84:16:84:16 | [Literal] 1 | semmle.order | 1 | | file://:0:0:0:0 | (Arguments) | arrays.js:5:8:5:14 | [DotExpr] obj.foo | semmle.label | 0 | | file://:0:0:0:0 | (Arguments) | arrays.js:5:8:5:14 | [DotExpr] obj.foo | semmle.order | 0 | | file://:0:0:0:0 | (Arguments) | arrays.js:8:12:8:17 | [VarRef] source | semmle.label | 0 | @@ -1140,6 +1169,10 @@ edges | file://:0:0:0:0 | (Arguments) | arrays.js:80:24:80:26 | [VarRef] arr | semmle.order | 0 | | file://:0:0:0:0 | (Arguments) | arrays.js:81:10:81:10 | [VarRef] x | semmle.label | 0 | | file://:0:0:0:0 | (Arguments) | arrays.js:81:10:81:10 | [VarRef] x | semmle.order | 0 | +| file://:0:0:0:0 | (Arguments) | arrays.js:84:8:84:17 | [MethodCallExpr] arr.at(-1) | semmle.label | 0 | +| file://:0:0:0:0 | (Arguments) | arrays.js:84:8:84:17 | [MethodCallExpr] arr.at(-1) | semmle.order | 0 | +| file://:0:0:0:0 | (Arguments) | arrays.js:84:15:84:16 | [UnaryExpr] -1 | semmle.label | 0 | +| file://:0:0:0:0 | (Arguments) | arrays.js:84:15:84:16 | [UnaryExpr] -1 | semmle.order | 0 | | file://:0:0:0:0 | (Parameters) | arrays.js:15:16:15:16 | [SimpleParameter] e | semmle.label | 0 | | file://:0:0:0:0 | (Parameters) | arrays.js:15:16:15:16 | [SimpleParameter] e | semmle.order | 0 | | file://:0:0:0:0 | (Parameters) | arrays.js:16:12:16:12 | [SimpleParameter] e | semmle.label | 0 | diff --git a/javascript/ql/test/library-tests/Routing/test.ql b/javascript/ql/test/library-tests/Routing/test.ql index aa89c7212d2..b427f710894 100644 --- a/javascript/ql/test/library-tests/Routing/test.ql +++ b/javascript/ql/test/library-tests/Routing/test.ql @@ -9,12 +9,12 @@ class Taint extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node node) { node.(DataFlow::CallNode).getCalleeName() = "source" or - node = testInstance().getMember("getSource").getReturn().getAnImmediateUse() + node = testInstance().getMember("getSource").getReturn().asSource() } override predicate isSink(DataFlow::Node node) { node = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument() or - node = testInstance().getMember("getSink").getAParameter().getARhs() + node = testInstance().getMember("getSink").getAParameter().asSink() } } diff --git a/javascript/ql/test/library-tests/TaintBarriers/tests.expected b/javascript/ql/test/library-tests/TaintBarriers/tests.expected index 33f06a2ff23..3ee1223b87d 100644 --- a/javascript/ql/test/library-tests/TaintBarriers/tests.expected +++ b/javascript/ql/test/library-tests/TaintBarriers/tests.expected @@ -43,6 +43,10 @@ isLabeledBarrier | ExampleConfiguration | tst.js:361:14:361:14 | v | taint | | ExampleConfiguration | tst.js:371:14:371:16 | o.p | taint | | ExampleConfiguration | tst.js:378:14:378:17 | o[p] | taint | +| ExampleConfiguration | tst.js:392:14:392:14 | v | taint | +| ExampleConfiguration | tst.js:394:14:394:16 | v.p | taint | +| ExampleConfiguration | tst.js:396:14:396:18 | v.p.q | taint | +| ExampleConfiguration | tst.js:402:14:402:14 | v | taint | isSanitizer | ExampleConfiguration | tst.js:176:18:176:18 | v | sanitizingGuard @@ -122,6 +126,12 @@ sanitizingGuard | tst.js:370:9:370:29 | o.p == ... listed" | tst.js:370:16:370:29 | "white-listed" | true | | tst.js:377:11:377:32 | o[p] == ... listed" | tst.js:377:11:377:14 | o[p] | true | | tst.js:377:11:377:32 | o[p] == ... listed" | tst.js:377:19:377:32 | "white-listed" | true | +| tst.js:391:9:391:27 | o.hasOwnProperty(v) | tst.js:391:26:391:26 | v | true | +| tst.js:393:16:393:36 | o.hasOw ... ty(v.p) | tst.js:393:33:393:35 | v.p | true | +| tst.js:395:16:395:38 | o.hasOw ... (v.p.q) | tst.js:395:33:395:37 | v.p.q | true | +| tst.js:397:16:397:36 | o.hasOw ... ty(v.p) | tst.js:397:33:397:35 | v.p | true | +| tst.js:399:16:399:41 | o.hasOw ... "p.q"]) | tst.js:399:33:399:40 | v["p.q"] | true | +| tst.js:401:16:401:34 | Object.hasOwn(o, v) | tst.js:401:33:401:33 | v | true | taintedSink | tst.js:2:13:2:20 | SOURCE() | tst.js:3:10:3:10 | v | | tst.js:2:13:2:20 | SOURCE() | tst.js:8:14:8:14 | v | @@ -186,3 +196,6 @@ taintedSink | tst.js:367:13:367:20 | SOURCE() | tst.js:373:14:373:16 | o.p | | tst.js:367:13:367:20 | SOURCE() | tst.js:380:14:380:17 | o[p] | | tst.js:367:13:367:20 | SOURCE() | tst.js:382:14:382:17 | o[p] | +| tst.js:388:13:388:20 | SOURCE() | tst.js:389:10:389:14 | v.p.q | +| tst.js:388:13:388:20 | SOURCE() | tst.js:398:14:398:14 | v | +| tst.js:388:13:388:20 | SOURCE() | tst.js:400:14:400:18 | v.p.q | diff --git a/javascript/ql/test/library-tests/TaintBarriers/tst.js b/javascript/ql/test/library-tests/TaintBarriers/tst.js index 265c5ac6210..20102605af1 100644 --- a/javascript/ql/test/library-tests/TaintBarriers/tst.js +++ b/javascript/ql/test/library-tests/TaintBarriers/tst.js @@ -383,3 +383,22 @@ function constantComparisonSanitizer2() { } } } + +function propertySanitization(o) { + var v = SOURCE(); + SINK(v.p.q); // NOT OK + + if (o.hasOwnProperty(v)) { + SINK(v); // OK + } else if (o.hasOwnProperty(v.p)) { + SINK(v.p); // OK + } else if (o.hasOwnProperty(v.p.q)) { + SINK(v.p.q); // OK + } else if (o.hasOwnProperty(v.p)) { + SINK(v); // NOT OK + } else if (o.hasOwnProperty(v["p.q"])) { + SINK(v.p.q); // NOT OK + } else if (Object.hasOwn(o, v)) { + SINK(v); // OK + } +} diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index 12fdd69e80f..cd41461e4a8 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -17,6 +17,7 @@ typeInferenceMismatch | arrays.js:2:15:2:22 | source() | arrays.js:11:10:11:28 | union(["bla"], foo) | | arrays.js:2:15:2:22 | source() | arrays.js:14:10:14:18 | flat(foo) | | arrays.js:2:15:2:22 | source() | arrays.js:19:10:19:12 | res | +| arrays.js:2:15:2:22 | source() | arrays.js:21:10:21:19 | foo.at(-1) | | booleanOps.js:2:11:2:18 | source() | booleanOps.js:4:8:4:8 | x | | booleanOps.js:2:11:2:18 | source() | booleanOps.js:13:10:13:10 | x | | booleanOps.js:2:11:2:18 | source() | booleanOps.js:19:10:19:10 | x | diff --git a/javascript/ql/test/library-tests/TaintTracking/arrays.js b/javascript/ql/test/library-tests/TaintTracking/arrays.js index b99e2058d80..b9daf4943b3 100644 --- a/javascript/ql/test/library-tests/TaintTracking/arrays.js +++ b/javascript/ql/test/library-tests/TaintTracking/arrays.js @@ -17,4 +17,6 @@ function test() { return prev + '' + current + ''; }, ''); sink(res); // NOT OK + + sink(foo.at(-1)); // NOT OK } diff --git a/javascript/ql/test/library-tests/TypeScript/Types/printAst.expected b/javascript/ql/test/library-tests/TypeScript/Types/printAst.expected index e50154887a2..9f4f6c6aba5 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/printAst.expected +++ b/javascript/ql/test/library-tests/TypeScript/Types/printAst.expected @@ -94,6 +94,11 @@ nodes | file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | | file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | | file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | +| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | +| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | +| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | +| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | +| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) | | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | @@ -120,6 +125,17 @@ nodes | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | | file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) | +| file://:0:0:0:0 | (TypeParameters) | semmle.label | (TypeParameters) | +| file://:0:0:0:0 | (TypeParameters) | semmle.label | (TypeParameters) | +| file://:0:0:0:0 | (TypeParameters) | semmle.label | (TypeParameters) | +| file://:0:0:0:0 | (TypeParameters) | semmle.label | (TypeParameters) | | file://:0:0:0:0 | (TypeParameters) | semmle.label | (TypeParameters) | | file://:0:0:0:0 | (TypeParameters) | semmle.label | (TypeParameters) | | file://:0:0:0:0 | (TypeParameters) | semmle.label | (TypeParameters) | @@ -1128,17 +1144,314 @@ nodes | tst.ts:293:7:293:23 | [MethodCallExpr] payload.toFixed() | semmle.label | [MethodCallExpr] payload.toFixed() | | tst.ts:293:7:293:24 | [ExprStmt] payload.toFixed(); | semmle.label | [ExprStmt] payload.toFixed(); | | tst.ts:293:15:293:21 | [Label] toFixed | semmle.label | [Label] toFixed | +| tst.ts:298:1:298:21 | [DeclStmt] const key = ... | semmle.label | [DeclStmt] const key = ... | +| tst.ts:298:1:298:21 | [DeclStmt] const key = ... | semmle.order | 61 | +| tst.ts:298:7:298:9 | [VarDecl] key | semmle.label | [VarDecl] key | +| tst.ts:298:7:298:20 | [VariableDeclarator] key = Symbol() | semmle.label | [VariableDeclarator] key = Symbol() | +| tst.ts:298:13:298:18 | [VarRef] Symbol | semmle.label | [VarRef] Symbol | +| tst.ts:298:13:298:20 | [CallExpr] Symbol() | semmle.label | [CallExpr] Symbol() | +| tst.ts:300:1:300:58 | [DeclStmt] const numberOrString = ... | semmle.label | [DeclStmt] const numberOrString = ... | +| tst.ts:300:1:300:58 | [DeclStmt] const numberOrString = ... | semmle.order | 62 | +| tst.ts:300:7:300:20 | [VarDecl] numberOrString | semmle.label | [VarDecl] numberOrString | +| tst.ts:300:7:300:57 | [VariableDeclarator] numberO ... "hello" | semmle.label | [VariableDeclarator] numberO ... "hello" | +| tst.ts:300:24:300:27 | [VarRef] Math | semmle.label | [VarRef] Math | +| tst.ts:300:24:300:34 | [DotExpr] Math.random | semmle.label | [DotExpr] Math.random | +| tst.ts:300:24:300:36 | [MethodCallExpr] Math.random() | semmle.label | [MethodCallExpr] Math.random() | +| tst.ts:300:24:300:42 | [BinaryExpr] Math.random() < 0.5 | semmle.label | [BinaryExpr] Math.random() < 0.5 | +| tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | semmle.label | [ConditionalExpr] Math.ra ... "hello" | +| tst.ts:300:29:300:34 | [Label] random | semmle.label | [Label] random | +| tst.ts:300:40:300:42 | [Literal] 0.5 | semmle.label | [Literal] 0.5 | +| tst.ts:300:46:300:47 | [Literal] 42 | semmle.label | [Literal] 42 | +| tst.ts:300:51:300:57 | [Literal] "hello" | semmle.label | [Literal] "hello" | +| tst.ts:302:1:304:2 | [DeclStmt] let obj = ... | semmle.label | [DeclStmt] let obj = ... | +| tst.ts:302:1:304:2 | [DeclStmt] let obj = ... | semmle.order | 63 | +| tst.ts:302:5:302:7 | [VarDecl] obj | semmle.label | [VarDecl] obj | +| tst.ts:302:5:304:1 | [VariableDeclarator] obj = { ... ring, } | semmle.label | [VariableDeclarator] obj = { ... ring, } | +| tst.ts:302:11:304:1 | [ObjectExpr] { [ke ... ring, } | semmle.label | [ObjectExpr] { [ke ... ring, } | +| tst.ts:303:3:303:23 | [Property] [key]: ... rString | semmle.label | [Property] [key]: ... rString | +| tst.ts:303:4:303:6 | [VarRef] key | semmle.label | [VarRef] key | +| tst.ts:303:10:303:23 | [VarRef] numberOrString | semmle.label | [VarRef] numberOrString | +| tst.ts:306:1:309:1 | [IfStmt] if (typ ... se(); } | semmle.label | [IfStmt] if (typ ... se(); } | +| tst.ts:306:1:309:1 | [IfStmt] if (typ ... se(); } | semmle.order | 64 | +| tst.ts:306:5:306:19 | [UnaryExpr] typeof obj[key] | semmle.label | [UnaryExpr] typeof obj[key] | +| tst.ts:306:5:306:32 | [BinaryExpr] typeof ... string" | semmle.label | [BinaryExpr] typeof ... string" | +| tst.ts:306:12:306:14 | [VarRef] obj | semmle.label | [VarRef] obj | +| tst.ts:306:12:306:19 | [IndexExpr] obj[key] | semmle.label | [IndexExpr] obj[key] | +| tst.ts:306:16:306:18 | [VarRef] key | semmle.label | [VarRef] key | +| tst.ts:306:25:306:32 | [Literal] "string" | semmle.label | [Literal] "string" | +| tst.ts:306:35:309:1 | [BlockStmt] { let ... se(); } | semmle.label | [BlockStmt] { let ... se(); } | +| tst.ts:307:3:307:21 | [DeclStmt] let str = ... | semmle.label | [DeclStmt] let str = ... | +| tst.ts:307:7:307:9 | [VarDecl] str | semmle.label | [VarDecl] str | +| tst.ts:307:7:307:20 | [VariableDeclarator] str = obj[key] | semmle.label | [VariableDeclarator] str = obj[key] | +| tst.ts:307:13:307:15 | [VarRef] obj | semmle.label | [VarRef] obj | +| tst.ts:307:13:307:20 | [IndexExpr] obj[key] | semmle.label | [IndexExpr] obj[key] | +| tst.ts:307:17:307:19 | [VarRef] key | semmle.label | [VarRef] key | +| tst.ts:308:3:308:5 | [VarRef] str | semmle.label | [VarRef] str | +| tst.ts:308:3:308:17 | [DotExpr] str.toUpperCase | semmle.label | [DotExpr] str.toUpperCase | +| tst.ts:308:3:308:19 | [MethodCallExpr] str.toUpperCase() | semmle.label | [MethodCallExpr] str.toUpperCase() | +| tst.ts:308:3:308:20 | [ExprStmt] str.toUpperCase(); | semmle.label | [ExprStmt] str.toUpperCase(); | +| tst.ts:308:7:308:17 | [Label] toUpperCase | semmle.label | [Label] toUpperCase | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | semmle.label | [FunctionDeclStmt] functio ... void {} | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | semmle.order | 65 | +| tst.ts:313:10:313:10 | [VarDecl] f | semmle.label | [VarDecl] f | +| tst.ts:313:12:313:12 | [Identifier] T | semmle.label | [Identifier] T | +| tst.ts:313:12:313:12 | [TypeParameter] T | semmle.label | [TypeParameter] T | +| tst.ts:313:15:313:17 | [SimpleParameter] arg | semmle.label | [SimpleParameter] arg | +| tst.ts:313:20:315:27 | [InterfaceTypeExpr] { pro ... void } | semmle.label | [InterfaceTypeExpr] { pro ... void } | +| tst.ts:314:3:314:9 | [Label] produce | semmle.label | [Label] produce | +| tst.ts:314:3:314:28 | [FieldDeclaration] produce ... ) => T, | semmle.label | [FieldDeclaration] produce ... ) => T, | +| tst.ts:314:12:314:27 | [FunctionExpr] (n: string) => T | semmle.label | [FunctionExpr] (n: string) => T | +| tst.ts:314:12:314:27 | [FunctionTypeExpr] (n: string) => T | semmle.label | [FunctionTypeExpr] (n: string) => T | +| tst.ts:314:13:314:13 | [SimpleParameter] n | semmle.label | [SimpleParameter] n | +| tst.ts:314:16:314:21 | [KeywordTypeExpr] string | semmle.label | [KeywordTypeExpr] string | +| tst.ts:314:27:314:27 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | +| tst.ts:315:3:315:9 | [Label] consume | semmle.label | [Label] consume | +| tst.ts:315:3:315:25 | [FieldDeclaration] consume ... => void | semmle.label | [FieldDeclaration] consume ... => void | +| tst.ts:315:12:315:25 | [FunctionExpr] (x: T) => void | semmle.label | [FunctionExpr] (x: T) => void | +| tst.ts:315:12:315:25 | [FunctionTypeExpr] (x: T) => void | semmle.label | [FunctionTypeExpr] (x: T) => void | +| tst.ts:315:13:315:13 | [SimpleParameter] x | semmle.label | [SimpleParameter] x | +| tst.ts:315:16:315:16 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | +| tst.ts:315:22:315:25 | [KeywordTypeExpr] void | semmle.label | [KeywordTypeExpr] void | +| tst.ts:316:4:316:7 | [KeywordTypeExpr] void | semmle.label | [KeywordTypeExpr] void | +| tst.ts:316:9:316:10 | [BlockStmt] {} | semmle.label | [BlockStmt] {} | +| tst.ts:316:11:316:11 | [EmptyStmt] ; | semmle.label | [EmptyStmt] ; | +| tst.ts:316:11:316:11 | [EmptyStmt] ; | semmle.order | 66 | +| tst.ts:318:1:318:1 | [VarRef] f | semmle.label | [VarRef] f | +| tst.ts:318:1:321:2 | [CallExpr] f({ p ... se() }) | semmle.label | [CallExpr] f({ p ... se() }) | +| tst.ts:318:1:321:3 | [ExprStmt] f({ p ... e() }); | semmle.label | [ExprStmt] f({ p ... e() }); | +| tst.ts:318:1:321:3 | [ExprStmt] f({ p ... e() }); | semmle.order | 67 | +| tst.ts:318:3:321:1 | [ObjectExpr] {produce: ...} | semmle.label | [ObjectExpr] {produce: ...} | +| tst.ts:319:3:319:9 | [Label] produce | semmle.label | [Label] produce | +| tst.ts:319:3:319:17 | [Property] produce: n => n | semmle.label | [Property] produce: n => n | +| tst.ts:319:12:319:12 | [SimpleParameter] n | semmle.label | [SimpleParameter] n | +| tst.ts:319:12:319:17 | [ArrowFunctionExpr] n => n | semmle.label | [ArrowFunctionExpr] n => n | +| tst.ts:319:17:319:17 | [VarRef] n | semmle.label | [VarRef] n | +| tst.ts:320:3:320:9 | [Label] consume | semmle.label | [Label] consume | +| tst.ts:320:3:320:31 | [Property] consume ... rCase() | semmle.label | [Property] consume ... rCase() | +| tst.ts:320:12:320:12 | [SimpleParameter] x | semmle.label | [SimpleParameter] x | +| tst.ts:320:12:320:31 | [ArrowFunctionExpr] x => x.toLowerCase() | semmle.label | [ArrowFunctionExpr] x => x.toLowerCase() | +| tst.ts:320:17:320:17 | [VarRef] x | semmle.label | [VarRef] x | +| tst.ts:320:17:320:29 | [DotExpr] x.toLowerCase | semmle.label | [DotExpr] x.toLowerCase | +| tst.ts:320:17:320:31 | [MethodCallExpr] x.toLowerCase() | semmle.label | [MethodCallExpr] x.toLowerCase() | +| tst.ts:320:19:320:29 | [Label] toLowerCase | semmle.label | [Label] toLowerCase | +| tst.ts:325:1:325:36 | [DeclStmt] const ErrorMap = ... | semmle.label | [DeclStmt] const ErrorMap = ... | +| tst.ts:325:1:325:36 | [DeclStmt] const ErrorMap = ... | semmle.order | 68 | +| tst.ts:325:7:325:14 | [VarDecl] ErrorMap | semmle.label | [VarDecl] ErrorMap | +| tst.ts:325:7:325:35 | [VariableDeclarator] ErrorMa ... Error> | semmle.label | [VariableDeclarator] ErrorMa ... Error> | +| tst.ts:325:18:325:20 | [VarRef] Map | semmle.label | [VarRef] Map | +| tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | semmle.label | [ExpressionWithTypeArguments] Map | +| tst.ts:325:22:325:27 | [KeywordTypeExpr] string | semmle.label | [KeywordTypeExpr] string | +| tst.ts:325:30:325:34 | [LocalTypeAccess] Error | semmle.label | [LocalTypeAccess] Error | +| tst.ts:327:1:327:32 | [DeclStmt] const errorMap = ... | semmle.label | [DeclStmt] const errorMap = ... | +| tst.ts:327:1:327:32 | [DeclStmt] const errorMap = ... | semmle.order | 69 | +| tst.ts:327:7:327:14 | [VarDecl] errorMap | semmle.label | [VarDecl] errorMap | +| tst.ts:327:7:327:31 | [VariableDeclarator] errorMa ... orMap() | semmle.label | [VariableDeclarator] errorMa ... orMap() | +| tst.ts:327:18:327:31 | [NewExpr] new ErrorMap() | semmle.label | [NewExpr] new ErrorMap() | +| tst.ts:327:22:327:29 | [VarRef] ErrorMap | semmle.label | [VarRef] ErrorMap | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | semmle.label | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | semmle.order | 70 | +| tst.ts:331:6:331:16 | [Identifier] FirstString | semmle.label | [Identifier] FirstString | +| tst.ts:331:18:331:18 | [Identifier] T | semmle.label | [Identifier] T | +| tst.ts:331:18:331:18 | [TypeParameter] T | semmle.label | [TypeParameter] T | +| tst.ts:332:3:332:3 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | semmle.label | [ConditionalTypeExpr] T exten ... : never | +| tst.ts:332:13:332:50 | [TupleTypeExpr] [infer ... nown[]] | semmle.label | [TupleTypeExpr] [infer ... nown[]] | +| tst.ts:332:14:332:35 | [InferTypeExpr] infer S ... string | semmle.label | [InferTypeExpr] infer S ... string | +| tst.ts:332:20:332:20 | [Identifier] S | semmle.label | [Identifier] S | +| tst.ts:332:20:332:35 | [TypeParameter] S extends string | semmle.label | [TypeParameter] S extends string | +| tst.ts:332:30:332:35 | [KeywordTypeExpr] string | semmle.label | [KeywordTypeExpr] string | +| tst.ts:332:38:332:49 | [RestTypeExpr] ...unknown[] | semmle.label | [RestTypeExpr] ...unknown[] | +| tst.ts:332:41:332:47 | [KeywordTypeExpr] unknown | semmle.label | [KeywordTypeExpr] unknown | +| tst.ts:332:41:332:49 | [ArrayTypeExpr] unknown[] | semmle.label | [ArrayTypeExpr] unknown[] | +| tst.ts:333:9:333:9 | [LocalTypeAccess] S | semmle.label | [LocalTypeAccess] S | +| tst.ts:334:9:334:13 | [KeywordTypeExpr] never | semmle.label | [KeywordTypeExpr] never | +| tst.ts:336:1:336:51 | [TypeAliasDeclaration,TypeDefinition] type F ... lean]>; | semmle.label | [TypeAliasDeclaration,TypeDefinition] type F ... lean]>; | +| tst.ts:336:1:336:51 | [TypeAliasDeclaration,TypeDefinition] type F ... lean]>; | semmle.order | 71 | +| tst.ts:336:6:336:6 | [Identifier] F | semmle.label | [Identifier] F | +| tst.ts:336:10:336:20 | [LocalTypeAccess] FirstString | semmle.label | [LocalTypeAccess] FirstString | +| tst.ts:336:10:336:50 | [GenericTypeExpr] FirstSt ... olean]> | semmle.label | [GenericTypeExpr] FirstSt ... olean]> | +| tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | semmle.label | [TupleTypeExpr] ['a' \| ... oolean] | +| tst.ts:336:23:336:25 | [LiteralTypeExpr] 'a' | semmle.label | [LiteralTypeExpr] 'a' | +| tst.ts:336:23:336:31 | [UnionTypeExpr] 'a' \| 'b' | semmle.label | [UnionTypeExpr] 'a' \| 'b' | +| tst.ts:336:29:336:31 | [LiteralTypeExpr] 'b' | semmle.label | [LiteralTypeExpr] 'b' | +| tst.ts:336:34:336:39 | [KeywordTypeExpr] number | semmle.label | [KeywordTypeExpr] number | +| tst.ts:336:42:336:48 | [KeywordTypeExpr] boolean | semmle.label | [KeywordTypeExpr] boolean | +| tst.ts:338:1:338:17 | [DeclStmt] const a = ... | semmle.label | [DeclStmt] const a = ... | +| tst.ts:338:1:338:17 | [DeclStmt] const a = ... | semmle.order | 72 | +| tst.ts:338:7:338:7 | [VarDecl] a | semmle.label | [VarDecl] a | +| tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | semmle.label | [VariableDeclarator] a: F = 'a' | +| tst.ts:338:10:338:10 | [LocalTypeAccess] F | semmle.label | [LocalTypeAccess] F | +| tst.ts:338:14:338:16 | [Literal] 'a' | semmle.label | [Literal] 'a' | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | semmle.label | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | semmle.order | 73 | +| tst.ts:342:11:342:15 | [Identifier] State | semmle.label | [Identifier] State | +| tst.ts:342:17:342:24 | [TypeParameter] in out T | semmle.label | [TypeParameter] in out T | +| tst.ts:342:24:342:24 | [Identifier] T | semmle.label | [Identifier] T | +| tst.ts:343:3:343:5 | [Label] get | semmle.label | [Label] get | +| tst.ts:343:3:343:15 | [FieldDeclaration] get: () => T; | semmle.label | [FieldDeclaration] get: () => T; | +| tst.ts:343:8:343:14 | [FunctionExpr] () => T | semmle.label | [FunctionExpr] () => T | +| tst.ts:343:8:343:14 | [FunctionTypeExpr] () => T | semmle.label | [FunctionTypeExpr] () => T | +| tst.ts:343:14:343:14 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | +| tst.ts:344:3:344:5 | [Label] set | semmle.label | [Label] set | +| tst.ts:344:3:344:26 | [FieldDeclaration] set: (v ... > void; | semmle.label | [FieldDeclaration] set: (v ... > void; | +| tst.ts:344:8:344:25 | [FunctionExpr] (value: T) => void | semmle.label | [FunctionExpr] (value: T) => void | +| tst.ts:344:8:344:25 | [FunctionTypeExpr] (value: T) => void | semmle.label | [FunctionTypeExpr] (value: T) => void | +| tst.ts:344:9:344:13 | [SimpleParameter] value | semmle.label | [SimpleParameter] value | +| tst.ts:344:16:344:16 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | +| tst.ts:344:22:344:25 | [KeywordTypeExpr] void | semmle.label | [KeywordTypeExpr] void | +| tst.ts:347:1:350:1 | [DeclStmt] const state = ... | semmle.label | [DeclStmt] const state = ... | +| tst.ts:347:1:350:1 | [DeclStmt] const state = ... | semmle.order | 74 | +| tst.ts:347:7:347:11 | [VarDecl] state | semmle.label | [VarDecl] state | +| tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | semmle.label | [VariableDeclarator] state: ... > { } } | +| tst.ts:347:14:347:18 | [LocalTypeAccess] State | semmle.label | [LocalTypeAccess] State | +| tst.ts:347:14:347:26 | [GenericTypeExpr] State | semmle.label | [GenericTypeExpr] State | +| tst.ts:347:20:347:25 | [KeywordTypeExpr] number | semmle.label | [KeywordTypeExpr] number | +| tst.ts:347:30:350:1 | [ObjectExpr] {get: ...} | semmle.label | [ObjectExpr] {get: ...} | +| tst.ts:348:3:348:5 | [Label] get | semmle.label | [Label] get | +| tst.ts:348:3:348:15 | [Property] get: () => 42 | semmle.label | [Property] get: () => 42 | +| tst.ts:348:8:348:15 | [ArrowFunctionExpr] () => 42 | semmle.label | [ArrowFunctionExpr] () => 42 | +| tst.ts:348:14:348:15 | [Literal] 42 | semmle.label | [Literal] 42 | +| tst.ts:349:3:349:5 | [Label] set | semmle.label | [Label] set | +| tst.ts:349:3:349:21 | [Property] set: (value) => { } | semmle.label | [Property] set: (value) => { } | +| tst.ts:349:8:349:21 | [ArrowFunctionExpr] (value) => { } | semmle.label | [ArrowFunctionExpr] (value) => { } | +| tst.ts:349:9:349:13 | [SimpleParameter] value | semmle.label | [SimpleParameter] value | +| tst.ts:349:19:349:21 | [BlockStmt] { } | semmle.label | [BlockStmt] { } | +| tst.ts:352:1:352:29 | [DeclStmt] const fortyTwo = ... | semmle.label | [DeclStmt] const fortyTwo = ... | +| tst.ts:352:1:352:29 | [DeclStmt] const fortyTwo = ... | semmle.order | 75 | +| tst.ts:352:7:352:14 | [VarDecl] fortyTwo | semmle.label | [VarDecl] fortyTwo | +| tst.ts:352:7:352:28 | [VariableDeclarator] fortyTw ... e.get() | semmle.label | [VariableDeclarator] fortyTw ... e.get() | +| tst.ts:352:18:352:22 | [VarRef] state | semmle.label | [VarRef] state | +| tst.ts:352:18:352:26 | [DotExpr] state.get | semmle.label | [DotExpr] state.get | +| tst.ts:352:18:352:28 | [MethodCallExpr] state.get() | semmle.label | [MethodCallExpr] state.get() | +| tst.ts:352:24:352:26 | [Label] get | semmle.label | [Label] get | +| tst.ts:356:1:356:44 | [ImportDeclaration] import ... S.mjs'; | semmle.label | [ImportDeclaration] import ... S.mjs'; | +| tst.ts:356:1:356:44 | [ImportDeclaration] import ... S.mjs'; | semmle.order | 76 | +| tst.ts:356:8:356:18 | [ImportSpecifier] tstModuleES | semmle.label | [ImportSpecifier] tstModuleES | +| tst.ts:356:8:356:18 | [VarDecl] tstModuleES | semmle.label | [VarDecl] tstModuleES | +| tst.ts:356:25:356:43 | [Literal] './tstModuleES.mjs' | semmle.label | [Literal] './tstModuleES.mjs' | +| tst.ts:358:1:358:7 | [VarRef] console | semmle.label | [VarRef] console | +| tst.ts:358:1:358:11 | [DotExpr] console.log | semmle.label | [DotExpr] console.log | +| tst.ts:358:1:358:26 | [MethodCallExpr] console ... leES()) | semmle.label | [MethodCallExpr] console ... leES()) | +| tst.ts:358:1:358:27 | [ExprStmt] console ... eES()); | semmle.label | [ExprStmt] console ... eES()); | +| tst.ts:358:1:358:27 | [ExprStmt] console ... eES()); | semmle.order | 77 | +| tst.ts:358:9:358:11 | [Label] log | semmle.label | [Label] log | +| tst.ts:358:13:358:23 | [VarRef] tstModuleES | semmle.label | [VarRef] tstModuleES | +| tst.ts:358:13:358:25 | [CallExpr] tstModuleES() | semmle.label | [CallExpr] tstModuleES() | +| tst.ts:360:1:360:50 | [ImportDeclaration] import ... S.cjs'; | semmle.label | [ImportDeclaration] import ... S.cjs'; | +| tst.ts:360:1:360:50 | [ImportDeclaration] import ... S.cjs'; | semmle.order | 78 | +| tst.ts:360:10:360:21 | [ImportSpecifier] tstModuleCJS | semmle.label | [ImportSpecifier] tstModuleCJS | +| tst.ts:360:10:360:21 | [Label] tstModuleCJS | semmle.label | [Label] tstModuleCJS | +| tst.ts:360:10:360:21 | [VarDecl] tstModuleCJS | semmle.label | [VarDecl] tstModuleCJS | +| tst.ts:360:30:360:49 | [Literal] './tstModuleCJS.cjs' | semmle.label | [Literal] './tstModuleCJS.cjs' | +| tst.ts:362:1:362:7 | [VarRef] console | semmle.label | [VarRef] console | +| tst.ts:362:1:362:11 | [DotExpr] console.log | semmle.label | [DotExpr] console.log | +| tst.ts:362:1:362:27 | [MethodCallExpr] console ... eCJS()) | semmle.label | [MethodCallExpr] console ... eCJS()) | +| tst.ts:362:1:362:28 | [ExprStmt] console ... CJS()); | semmle.label | [ExprStmt] console ... CJS()); | +| tst.ts:362:1:362:28 | [ExprStmt] console ... CJS()); | semmle.order | 79 | +| tst.ts:362:9:362:11 | [Label] log | semmle.label | [Label] log | +| tst.ts:362:13:362:24 | [VarRef] tstModuleCJS | semmle.label | [VarRef] tstModuleCJS | +| tst.ts:362:13:362:26 | [CallExpr] tstModuleCJS() | semmle.label | [CallExpr] tstModuleCJS() | +| tst.ts:368:1:368:34 | [ImportDeclaration] import ... ffixA'; | semmle.label | [ImportDeclaration] import ... ffixA'; | +| tst.ts:368:1:368:34 | [ImportDeclaration] import ... ffixA'; | semmle.order | 80 | +| tst.ts:368:8:368:13 | [ImportSpecifier] * as A | semmle.label | [ImportSpecifier] * as A | +| tst.ts:368:13:368:13 | [VarDecl] A | semmle.label | [VarDecl] A | +| tst.ts:368:20:368:33 | [Literal] './tstSuffixA' | semmle.label | [Literal] './tstSuffixA' | +| tst.ts:370:1:370:7 | [VarRef] console | semmle.label | [VarRef] console | +| tst.ts:370:1:370:11 | [DotExpr] console.log | semmle.label | [DotExpr] console.log | +| tst.ts:370:1:370:29 | [MethodCallExpr] console ... File()) | semmle.label | [MethodCallExpr] console ... File()) | +| tst.ts:370:1:370:30 | [ExprStmt] console ... ile()); | semmle.label | [ExprStmt] console ... ile()); | +| tst.ts:370:1:370:30 | [ExprStmt] console ... ile()); | semmle.order | 81 | +| tst.ts:370:9:370:11 | [Label] log | semmle.label | [Label] log | +| tst.ts:370:13:370:13 | [VarRef] A | semmle.label | [VarRef] A | +| tst.ts:370:13:370:26 | [DotExpr] A.resolvedFile | semmle.label | [DotExpr] A.resolvedFile | +| tst.ts:370:13:370:28 | [MethodCallExpr] A.resolvedFile() | semmle.label | [MethodCallExpr] A.resolvedFile() | +| tst.ts:370:15:370:26 | [Label] resolvedFile | semmle.label | [Label] resolvedFile | +| tst.ts:372:1:372:34 | [ImportDeclaration] import ... ffixB'; | semmle.label | [ImportDeclaration] import ... ffixB'; | +| tst.ts:372:1:372:34 | [ImportDeclaration] import ... ffixB'; | semmle.order | 82 | +| tst.ts:372:8:372:13 | [ImportSpecifier] * as B | semmle.label | [ImportSpecifier] * as B | +| tst.ts:372:13:372:13 | [VarDecl] B | semmle.label | [VarDecl] B | +| tst.ts:372:20:372:33 | [Literal] './tstSuffixB' | semmle.label | [Literal] './tstSuffixB' | +| tst.ts:374:1:374:7 | [VarRef] console | semmle.label | [VarRef] console | +| tst.ts:374:1:374:11 | [DotExpr] console.log | semmle.label | [DotExpr] console.log | +| tst.ts:374:1:374:29 | [MethodCallExpr] console ... File()) | semmle.label | [MethodCallExpr] console ... File()) | +| tst.ts:374:1:374:30 | [ExprStmt] console ... ile()); | semmle.label | [ExprStmt] console ... ile()); | +| tst.ts:374:1:374:30 | [ExprStmt] console ... ile()); | semmle.order | 83 | +| tst.ts:374:9:374:11 | [Label] log | semmle.label | [Label] log | +| tst.ts:374:13:374:13 | [VarRef] B | semmle.label | [VarRef] B | +| tst.ts:374:13:374:26 | [DotExpr] B.resolvedFile | semmle.label | [DotExpr] B.resolvedFile | +| tst.ts:374:13:374:28 | [MethodCallExpr] B.resolvedFile() | semmle.label | [MethodCallExpr] B.resolvedFile() | +| tst.ts:374:15:374:26 | [Label] resolvedFile | semmle.label | [Label] resolvedFile | +| tstModuleCJS.cts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | semmle.label | [ExportDeclaration] export ... 'b'; } | +| tstModuleCJS.cts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | semmle.order | 84 | +| tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | semmle.label | [FunctionDeclStmt] functio ... 'b'; } | +| tstModuleCJS.cts:1:17:1:28 | [VarDecl] tstModuleCJS | semmle.label | [VarDecl] tstModuleCJS | +| tstModuleCJS.cts:1:33:1:35 | [LiteralTypeExpr] 'a' | semmle.label | [LiteralTypeExpr] 'a' | +| tstModuleCJS.cts:1:33:1:41 | [UnionTypeExpr] 'a' \| 'b' | semmle.label | [UnionTypeExpr] 'a' \| 'b' | +| tstModuleCJS.cts:1:39:1:41 | [LiteralTypeExpr] 'b' | semmle.label | [LiteralTypeExpr] 'b' | +| tstModuleCJS.cts:1:43:3:1 | [BlockStmt] { r ... 'b'; } | semmle.label | [BlockStmt] { r ... 'b'; } | +| tstModuleCJS.cts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | semmle.label | [ReturnStmt] return ... : 'b'; | +| tstModuleCJS.cts:2:12:2:15 | [VarRef] Math | semmle.label | [VarRef] Math | +| tstModuleCJS.cts:2:12:2:22 | [DotExpr] Math.random | semmle.label | [DotExpr] Math.random | +| tstModuleCJS.cts:2:12:2:24 | [MethodCallExpr] Math.random() | semmle.label | [MethodCallExpr] Math.random() | +| tstModuleCJS.cts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | semmle.label | [BinaryExpr] Math.random() > 0.5 | +| tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | semmle.label | [ConditionalExpr] Math.ra ... ' : 'b' | +| tstModuleCJS.cts:2:17:2:22 | [Label] random | semmle.label | [Label] random | +| tstModuleCJS.cts:2:28:2:30 | [Literal] 0.5 | semmle.label | [Literal] 0.5 | +| tstModuleCJS.cts:2:34:2:36 | [Literal] 'a' | semmle.label | [Literal] 'a' | +| tstModuleCJS.cts:2:40:2:42 | [Literal] 'b' | semmle.label | [Literal] 'b' | +| tstModuleES.mts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | semmle.label | [ExportDeclaration] export ... 'b'; } | +| tstModuleES.mts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | semmle.order | 85 | +| tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | semmle.label | [FunctionDeclStmt] functio ... 'b'; } | +| tstModuleES.mts:1:25:1:35 | [VarDecl] tstModuleES | semmle.label | [VarDecl] tstModuleES | +| tstModuleES.mts:1:40:1:42 | [LiteralTypeExpr] 'a' | semmle.label | [LiteralTypeExpr] 'a' | +| tstModuleES.mts:1:40:1:48 | [UnionTypeExpr] 'a' \| 'b' | semmle.label | [UnionTypeExpr] 'a' \| 'b' | +| tstModuleES.mts:1:46:1:48 | [LiteralTypeExpr] 'b' | semmle.label | [LiteralTypeExpr] 'b' | +| tstModuleES.mts:1:50:3:1 | [BlockStmt] { r ... 'b'; } | semmle.label | [BlockStmt] { r ... 'b'; } | +| tstModuleES.mts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | semmle.label | [ReturnStmt] return ... : 'b'; | +| tstModuleES.mts:2:12:2:15 | [VarRef] Math | semmle.label | [VarRef] Math | +| tstModuleES.mts:2:12:2:22 | [DotExpr] Math.random | semmle.label | [DotExpr] Math.random | +| tstModuleES.mts:2:12:2:24 | [MethodCallExpr] Math.random() | semmle.label | [MethodCallExpr] Math.random() | +| tstModuleES.mts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | semmle.label | [BinaryExpr] Math.random() > 0.5 | +| tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | semmle.label | [ConditionalExpr] Math.ra ... ' : 'b' | +| tstModuleES.mts:2:17:2:22 | [Label] random | semmle.label | [Label] random | +| tstModuleES.mts:2:28:2:30 | [Literal] 0.5 | semmle.label | [Literal] 0.5 | +| tstModuleES.mts:2:34:2:36 | [Literal] 'a' | semmle.label | [Literal] 'a' | +| tstModuleES.mts:2:40:2:42 | [Literal] 'b' | semmle.label | [Literal] 'b' | +| tstSuffixA.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | semmle.label | [ExportDeclaration] export ... .ts'; } | +| tstSuffixA.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | semmle.order | 86 | +| tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.label | [FunctionDeclStmt] functio ... .ts'; } | +| tstSuffixA.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.label | [VarDecl] resolvedFile | +| tstSuffixA.ts:1:33:1:47 | [LiteralTypeExpr] 'tstSuffixA.ts' | semmle.label | [LiteralTypeExpr] 'tstSuffixA.ts' | +| tstSuffixA.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | semmle.label | [BlockStmt] { r ... .ts'; } | +| tstSuffixA.ts:2:5:2:27 | [ReturnStmt] return ... xA.ts'; | semmle.label | [ReturnStmt] return ... xA.ts'; | +| tstSuffixA.ts:2:12:2:26 | [Literal] 'tstSuffixA.ts' | semmle.label | [Literal] 'tstSuffixA.ts' | +| tstSuffixB.ios.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | semmle.label | [ExportDeclaration] export ... .ts'; } | +| tstSuffixB.ios.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | semmle.order | 87 | +| tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.label | [FunctionDeclStmt] functio ... .ts'; } | +| tstSuffixB.ios.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.label | [VarDecl] resolvedFile | +| tstSuffixB.ios.ts:1:33:1:51 | [LiteralTypeExpr] 'tstSuffixB.ios.ts' | semmle.label | [LiteralTypeExpr] 'tstSuffixB.ios.ts' | +| tstSuffixB.ios.ts:1:53:3:1 | [BlockStmt] { r ... .ts'; } | semmle.label | [BlockStmt] { r ... .ts'; } | +| tstSuffixB.ios.ts:2:5:2:31 | [ReturnStmt] return ... os.ts'; | semmle.label | [ReturnStmt] return ... os.ts'; | +| tstSuffixB.ios.ts:2:12:2:30 | [Literal] 'tstSuffixB.ios.ts' | semmle.label | [Literal] 'tstSuffixB.ios.ts' | +| tstSuffixB.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | semmle.label | [ExportDeclaration] export ... .ts'; } | +| tstSuffixB.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | semmle.order | 88 | +| tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.label | [FunctionDeclStmt] functio ... .ts'; } | +| tstSuffixB.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.label | [VarDecl] resolvedFile | +| tstSuffixB.ts:1:33:1:47 | [LiteralTypeExpr] 'tstSuffixB.ts' | semmle.label | [LiteralTypeExpr] 'tstSuffixB.ts' | +| tstSuffixB.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | semmle.label | [BlockStmt] { r ... .ts'; } | +| tstSuffixB.ts:2:5:2:27 | [ReturnStmt] return ... xB.ts'; | semmle.label | [ReturnStmt] return ... xB.ts'; | +| tstSuffixB.ts:2:12:2:26 | [Literal] 'tstSuffixB.ts' | semmle.label | [Literal] 'tstSuffixB.ts' | | type_alias.ts:1:1:1:17 | [TypeAliasDeclaration,TypeDefinition] type B = boolean; | semmle.label | [TypeAliasDeclaration,TypeDefinition] type B = boolean; | -| type_alias.ts:1:1:1:17 | [TypeAliasDeclaration,TypeDefinition] type B = boolean; | semmle.order | 61 | +| type_alias.ts:1:1:1:17 | [TypeAliasDeclaration,TypeDefinition] type B = boolean; | semmle.order | 89 | | type_alias.ts:1:6:1:6 | [Identifier] B | semmle.label | [Identifier] B | | type_alias.ts:1:10:1:16 | [KeywordTypeExpr] boolean | semmle.label | [KeywordTypeExpr] boolean | | type_alias.ts:3:1:3:9 | [DeclStmt] var b = ... | semmle.label | [DeclStmt] var b = ... | -| type_alias.ts:3:1:3:9 | [DeclStmt] var b = ... | semmle.order | 62 | +| type_alias.ts:3:1:3:9 | [DeclStmt] var b = ... | semmle.order | 90 | | type_alias.ts:3:5:3:5 | [VarDecl] b | semmle.label | [VarDecl] b | | type_alias.ts:3:5:3:8 | [VariableDeclarator] b: B | semmle.label | [VariableDeclarator] b: B | | type_alias.ts:3:8:3:8 | [LocalTypeAccess] B | semmle.label | [LocalTypeAccess] B | | type_alias.ts:5:1:5:50 | [TypeAliasDeclaration,TypeDefinition] type Va ... ay>; | semmle.label | [TypeAliasDeclaration,TypeDefinition] type Va ... ay>; | -| type_alias.ts:5:1:5:50 | [TypeAliasDeclaration,TypeDefinition] type Va ... ay>; | semmle.order | 63 | +| type_alias.ts:5:1:5:50 | [TypeAliasDeclaration,TypeDefinition] type Va ... ay>; | semmle.order | 91 | | type_alias.ts:5:6:5:17 | [Identifier] ValueOrArray | semmle.label | [Identifier] ValueOrArray | | type_alias.ts:5:19:5:19 | [Identifier] T | semmle.label | [Identifier] T | | type_alias.ts:5:19:5:19 | [TypeParameter] T | semmle.label | [TypeParameter] T | @@ -1150,14 +1463,14 @@ nodes | type_alias.ts:5:34:5:48 | [GenericTypeExpr] ValueOrArray | semmle.label | [GenericTypeExpr] ValueOrArray | | type_alias.ts:5:47:5:47 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | | type_alias.ts:7:1:7:28 | [DeclStmt] var c = ... | semmle.label | [DeclStmt] var c = ... | -| type_alias.ts:7:1:7:28 | [DeclStmt] var c = ... | semmle.order | 64 | +| type_alias.ts:7:1:7:28 | [DeclStmt] var c = ... | semmle.order | 92 | | type_alias.ts:7:5:7:5 | [VarDecl] c | semmle.label | [VarDecl] c | | type_alias.ts:7:5:7:27 | [VariableDeclarator] c: Valu ... number> | semmle.label | [VariableDeclarator] c: Valu ... number> | | type_alias.ts:7:8:7:19 | [LocalTypeAccess] ValueOrArray | semmle.label | [LocalTypeAccess] ValueOrArray | | type_alias.ts:7:8:7:27 | [GenericTypeExpr] ValueOrArray | semmle.label | [GenericTypeExpr] ValueOrArray | | type_alias.ts:7:21:7:26 | [KeywordTypeExpr] number | semmle.label | [KeywordTypeExpr] number | | type_alias.ts:9:1:15:13 | [TypeAliasDeclaration,TypeDefinition] type Js ... Json[]; | semmle.label | [TypeAliasDeclaration,TypeDefinition] type Js ... Json[]; | -| type_alias.ts:9:1:15:13 | [TypeAliasDeclaration,TypeDefinition] type Js ... Json[]; | semmle.order | 65 | +| type_alias.ts:9:1:15:13 | [TypeAliasDeclaration,TypeDefinition] type Js ... Json[]; | semmle.order | 93 | | type_alias.ts:9:6:9:9 | [Identifier] Json | semmle.label | [Identifier] Json | | type_alias.ts:10:5:15:12 | [UnionTypeExpr] \| strin ... Json[] | semmle.label | [UnionTypeExpr] \| strin ... Json[] | | type_alias.ts:10:7:10:12 | [KeywordTypeExpr] string | semmle.label | [KeywordTypeExpr] string | @@ -1173,12 +1486,12 @@ nodes | type_alias.ts:15:7:15:10 | [LocalTypeAccess] Json | semmle.label | [LocalTypeAccess] Json | | type_alias.ts:15:7:15:12 | [ArrayTypeExpr] Json[] | semmle.label | [ArrayTypeExpr] Json[] | | type_alias.ts:17:1:17:15 | [DeclStmt] var json = ... | semmle.label | [DeclStmt] var json = ... | -| type_alias.ts:17:1:17:15 | [DeclStmt] var json = ... | semmle.order | 66 | +| type_alias.ts:17:1:17:15 | [DeclStmt] var json = ... | semmle.order | 94 | | type_alias.ts:17:5:17:8 | [VarDecl] json | semmle.label | [VarDecl] json | | type_alias.ts:17:5:17:14 | [VariableDeclarator] json: Json | semmle.label | [VariableDeclarator] json: Json | | type_alias.ts:17:11:17:14 | [LocalTypeAccess] Json | semmle.label | [LocalTypeAccess] Json | | type_alias.ts:19:1:21:57 | [TypeAliasDeclaration,TypeDefinition] type Vi ... ode[]]; | semmle.label | [TypeAliasDeclaration,TypeDefinition] type Vi ... ode[]]; | -| type_alias.ts:19:1:21:57 | [TypeAliasDeclaration,TypeDefinition] type Vi ... ode[]]; | semmle.order | 67 | +| type_alias.ts:19:1:21:57 | [TypeAliasDeclaration,TypeDefinition] type Vi ... ode[]]; | semmle.order | 95 | | type_alias.ts:19:6:19:16 | [Identifier] VirtualNode | semmle.label | [Identifier] VirtualNode | | type_alias.ts:20:5:21:56 | [UnionTypeExpr] \| strin ... Node[]] | semmle.label | [UnionTypeExpr] \| strin ... Node[]] | | type_alias.ts:20:7:20:12 | [KeywordTypeExpr] string | semmle.label | [KeywordTypeExpr] string | @@ -1194,7 +1507,7 @@ nodes | type_alias.ts:21:43:21:53 | [LocalTypeAccess] VirtualNode | semmle.label | [LocalTypeAccess] VirtualNode | | type_alias.ts:21:43:21:55 | [ArrayTypeExpr] VirtualNode[] | semmle.label | [ArrayTypeExpr] VirtualNode[] | | type_alias.ts:23:1:27:6 | [DeclStmt] const myNode = ... | semmle.label | [DeclStmt] const myNode = ... | -| type_alias.ts:23:1:27:6 | [DeclStmt] const myNode = ... | semmle.order | 68 | +| type_alias.ts:23:1:27:6 | [DeclStmt] const myNode = ... | semmle.order | 96 | | type_alias.ts:23:7:23:12 | [VarDecl] myNode | semmle.label | [VarDecl] myNode | | type_alias.ts:23:7:27:5 | [VariableDeclarator] myNode: ... ] ] | semmle.label | [VariableDeclarator] myNode: ... ] ] | | type_alias.ts:23:15:23:25 | [LocalTypeAccess] VirtualNode | semmle.label | [LocalTypeAccess] VirtualNode | @@ -1219,12 +1532,12 @@ nodes | type_alias.ts:26:23:26:36 | [Literal] "second-child" | semmle.label | [Literal] "second-child" | | type_alias.ts:26:41:26:62 | [Literal] "I'm the second child" | semmle.label | [Literal] "I'm the second child" | | type_definition_objects.ts:1:1:1:33 | [ImportDeclaration] import ... dummy"; | semmle.label | [ImportDeclaration] import ... dummy"; | -| type_definition_objects.ts:1:1:1:33 | [ImportDeclaration] import ... dummy"; | semmle.order | 69 | +| type_definition_objects.ts:1:1:1:33 | [ImportDeclaration] import ... dummy"; | semmle.order | 97 | | type_definition_objects.ts:1:8:1:17 | [ImportSpecifier] * as dummy | semmle.label | [ImportSpecifier] * as dummy | | type_definition_objects.ts:1:13:1:17 | [VarDecl] dummy | semmle.label | [VarDecl] dummy | | type_definition_objects.ts:1:24:1:32 | [Literal] "./dummy" | semmle.label | [Literal] "./dummy" | | type_definition_objects.ts:3:1:3:17 | [ExportDeclaration] export class C {} | semmle.label | [ExportDeclaration] export class C {} | -| type_definition_objects.ts:3:1:3:17 | [ExportDeclaration] export class C {} | semmle.order | 70 | +| type_definition_objects.ts:3:1:3:17 | [ExportDeclaration] export class C {} | semmle.order | 98 | | type_definition_objects.ts:3:8:3:17 | [ClassDefinition,TypeDefinition] class C {} | semmle.label | [ClassDefinition,TypeDefinition] class C {} | | type_definition_objects.ts:3:14:3:14 | [VarDecl] C | semmle.label | [VarDecl] C | | type_definition_objects.ts:3:16:3:15 | [BlockStmt] {} | semmle.label | [BlockStmt] {} | @@ -1232,36 +1545,36 @@ nodes | type_definition_objects.ts:3:16:3:15 | [FunctionExpr] () {} | semmle.label | [FunctionExpr] () {} | | type_definition_objects.ts:3:16:3:15 | [Label] constructor | semmle.label | [Label] constructor | | type_definition_objects.ts:4:1:4:17 | [DeclStmt] let classObj = ... | semmle.label | [DeclStmt] let classObj = ... | -| type_definition_objects.ts:4:1:4:17 | [DeclStmt] let classObj = ... | semmle.order | 71 | +| type_definition_objects.ts:4:1:4:17 | [DeclStmt] let classObj = ... | semmle.order | 99 | | type_definition_objects.ts:4:5:4:12 | [VarDecl] classObj | semmle.label | [VarDecl] classObj | | type_definition_objects.ts:4:5:4:16 | [VariableDeclarator] classObj = C | semmle.label | [VariableDeclarator] classObj = C | | type_definition_objects.ts:4:16:4:16 | [VarRef] C | semmle.label | [VarRef] C | | type_definition_objects.ts:6:1:6:16 | [ExportDeclaration] export enum E {} | semmle.label | [ExportDeclaration] export enum E {} | -| type_definition_objects.ts:6:1:6:16 | [ExportDeclaration] export enum E {} | semmle.order | 72 | +| type_definition_objects.ts:6:1:6:16 | [ExportDeclaration] export enum E {} | semmle.order | 100 | | type_definition_objects.ts:6:8:6:16 | [EnumDeclaration,TypeDefinition] enum E {} | semmle.label | [EnumDeclaration,TypeDefinition] enum E {} | | type_definition_objects.ts:6:13:6:13 | [VarDecl] E | semmle.label | [VarDecl] E | | type_definition_objects.ts:7:1:7:16 | [DeclStmt] let enumObj = ... | semmle.label | [DeclStmt] let enumObj = ... | -| type_definition_objects.ts:7:1:7:16 | [DeclStmt] let enumObj = ... | semmle.order | 73 | +| type_definition_objects.ts:7:1:7:16 | [DeclStmt] let enumObj = ... | semmle.order | 101 | | type_definition_objects.ts:7:5:7:11 | [VarDecl] enumObj | semmle.label | [VarDecl] enumObj | | type_definition_objects.ts:7:5:7:15 | [VariableDeclarator] enumObj = E | semmle.label | [VariableDeclarator] enumObj = E | | type_definition_objects.ts:7:15:7:15 | [VarRef] E | semmle.label | [VarRef] E | | type_definition_objects.ts:9:1:9:22 | [ExportDeclaration] export ... e N {;} | semmle.label | [ExportDeclaration] export ... e N {;} | -| type_definition_objects.ts:9:1:9:22 | [ExportDeclaration] export ... e N {;} | semmle.order | 74 | +| type_definition_objects.ts:9:1:9:22 | [ExportDeclaration] export ... e N {;} | semmle.order | 102 | | type_definition_objects.ts:9:8:9:22 | [NamespaceDeclaration] namespace N {;} | semmle.label | [NamespaceDeclaration] namespace N {;} | | type_definition_objects.ts:9:18:9:18 | [VarDecl] N | semmle.label | [VarDecl] N | | type_definition_objects.ts:9:21:9:21 | [EmptyStmt] ; | semmle.label | [EmptyStmt] ; | | type_definition_objects.ts:10:1:10:21 | [DeclStmt] let namespaceObj = ... | semmle.label | [DeclStmt] let namespaceObj = ... | -| type_definition_objects.ts:10:1:10:21 | [DeclStmt] let namespaceObj = ... | semmle.order | 75 | +| type_definition_objects.ts:10:1:10:21 | [DeclStmt] let namespaceObj = ... | semmle.order | 103 | | type_definition_objects.ts:10:5:10:16 | [VarDecl] namespaceObj | semmle.label | [VarDecl] namespaceObj | | type_definition_objects.ts:10:5:10:20 | [VariableDeclarator] namespaceObj = N | semmle.label | [VariableDeclarator] namespaceObj = N | | type_definition_objects.ts:10:20:10:20 | [VarRef] N | semmle.label | [VarRef] N | | type_definitions.ts:1:1:1:33 | [ImportDeclaration] import ... dummy"; | semmle.label | [ImportDeclaration] import ... dummy"; | -| type_definitions.ts:1:1:1:33 | [ImportDeclaration] import ... dummy"; | semmle.order | 76 | +| type_definitions.ts:1:1:1:33 | [ImportDeclaration] import ... dummy"; | semmle.order | 104 | | type_definitions.ts:1:8:1:17 | [ImportSpecifier] * as dummy | semmle.label | [ImportSpecifier] * as dummy | | type_definitions.ts:1:13:1:17 | [VarDecl] dummy | semmle.label | [VarDecl] dummy | | type_definitions.ts:1:24:1:32 | [Literal] "./dummy" | semmle.label | [Literal] "./dummy" | | type_definitions.ts:3:1:5:1 | [InterfaceDeclaration,TypeDefinition] interfa ... x: S; } | semmle.label | [InterfaceDeclaration,TypeDefinition] interfa ... x: S; } | -| type_definitions.ts:3:1:5:1 | [InterfaceDeclaration,TypeDefinition] interfa ... x: S; } | semmle.order | 77 | +| type_definitions.ts:3:1:5:1 | [InterfaceDeclaration,TypeDefinition] interfa ... x: S; } | semmle.order | 105 | | type_definitions.ts:3:11:3:11 | [Identifier] I | semmle.label | [Identifier] I | | type_definitions.ts:3:13:3:13 | [Identifier] S | semmle.label | [Identifier] S | | type_definitions.ts:3:13:3:13 | [TypeParameter] S | semmle.label | [TypeParameter] S | @@ -1269,14 +1582,14 @@ nodes | type_definitions.ts:4:3:4:7 | [FieldDeclaration] x: S; | semmle.label | [FieldDeclaration] x: S; | | type_definitions.ts:4:6:4:6 | [LocalTypeAccess] S | semmle.label | [LocalTypeAccess] S | | type_definitions.ts:6:1:6:16 | [DeclStmt] let i = ... | semmle.label | [DeclStmt] let i = ... | -| type_definitions.ts:6:1:6:16 | [DeclStmt] let i = ... | semmle.order | 78 | +| type_definitions.ts:6:1:6:16 | [DeclStmt] let i = ... | semmle.order | 106 | | type_definitions.ts:6:5:6:5 | [VarDecl] i | semmle.label | [VarDecl] i | | type_definitions.ts:6:5:6:16 | [VariableDeclarator] i: I | semmle.label | [VariableDeclarator] i: I | | type_definitions.ts:6:8:6:8 | [LocalTypeAccess] I | semmle.label | [LocalTypeAccess] I | | type_definitions.ts:6:8:6:16 | [GenericTypeExpr] I | semmle.label | [GenericTypeExpr] I | | type_definitions.ts:6:10:6:15 | [KeywordTypeExpr] number | semmle.label | [KeywordTypeExpr] number | | type_definitions.ts:8:1:10:1 | [ClassDefinition,TypeDefinition] class C ... x: T } | semmle.label | [ClassDefinition,TypeDefinition] class C ... x: T } | -| type_definitions.ts:8:1:10:1 | [ClassDefinition,TypeDefinition] class C ... x: T } | semmle.order | 79 | +| type_definitions.ts:8:1:10:1 | [ClassDefinition,TypeDefinition] class C ... x: T } | semmle.order | 107 | | type_definitions.ts:8:7:8:7 | [VarDecl] C | semmle.label | [VarDecl] C | | type_definitions.ts:8:8:8:7 | [BlockStmt] {} | semmle.label | [BlockStmt] {} | | type_definitions.ts:8:8:8:7 | [ClassInitializedMember,ConstructorDefinition] constructor() {} | semmle.label | [ClassInitializedMember,ConstructorDefinition] constructor() {} | @@ -1288,14 +1601,14 @@ nodes | type_definitions.ts:9:3:9:6 | [FieldDeclaration] x: T | semmle.label | [FieldDeclaration] x: T | | type_definitions.ts:9:6:9:6 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | | type_definitions.ts:11:1:11:17 | [DeclStmt] let c = ... | semmle.label | [DeclStmt] let c = ... | -| type_definitions.ts:11:1:11:17 | [DeclStmt] let c = ... | semmle.order | 80 | +| type_definitions.ts:11:1:11:17 | [DeclStmt] let c = ... | semmle.order | 108 | | type_definitions.ts:11:5:11:5 | [VarDecl] c | semmle.label | [VarDecl] c | | type_definitions.ts:11:5:11:16 | [VariableDeclarator] c: C | semmle.label | [VariableDeclarator] c: C | | type_definitions.ts:11:8:11:8 | [LocalTypeAccess] C | semmle.label | [LocalTypeAccess] C | | type_definitions.ts:11:8:11:16 | [GenericTypeExpr] C | semmle.label | [GenericTypeExpr] C | | type_definitions.ts:11:10:11:15 | [KeywordTypeExpr] number | semmle.label | [KeywordTypeExpr] number | | type_definitions.ts:13:1:15:1 | [EnumDeclaration,TypeDefinition] enum Co ... blue } | semmle.label | [EnumDeclaration,TypeDefinition] enum Co ... blue } | -| type_definitions.ts:13:1:15:1 | [EnumDeclaration,TypeDefinition] enum Co ... blue } | semmle.order | 81 | +| type_definitions.ts:13:1:15:1 | [EnumDeclaration,TypeDefinition] enum Co ... blue } | semmle.order | 109 | | type_definitions.ts:13:6:13:10 | [VarDecl] Color | semmle.label | [VarDecl] Color | | type_definitions.ts:14:3:14:5 | [EnumMember,TypeDefinition] red | semmle.label | [EnumMember,TypeDefinition] red | | type_definitions.ts:14:3:14:5 | [VarDecl] red | semmle.label | [VarDecl] red | @@ -1304,29 +1617,29 @@ nodes | type_definitions.ts:14:15:14:18 | [EnumMember,TypeDefinition] blue | semmle.label | [EnumMember,TypeDefinition] blue | | type_definitions.ts:14:15:14:18 | [VarDecl] blue | semmle.label | [VarDecl] blue | | type_definitions.ts:16:1:16:17 | [DeclStmt] let color = ... | semmle.label | [DeclStmt] let color = ... | -| type_definitions.ts:16:1:16:17 | [DeclStmt] let color = ... | semmle.order | 82 | +| type_definitions.ts:16:1:16:17 | [DeclStmt] let color = ... | semmle.order | 110 | | type_definitions.ts:16:5:16:9 | [VarDecl] color | semmle.label | [VarDecl] color | | type_definitions.ts:16:5:16:16 | [VariableDeclarator] color: Color | semmle.label | [VariableDeclarator] color: Color | | type_definitions.ts:16:12:16:16 | [LocalTypeAccess] Color | semmle.label | [LocalTypeAccess] Color | | type_definitions.ts:18:1:18:33 | [EnumDeclaration,TypeDefinition] enum En ... ember } | semmle.label | [EnumDeclaration,TypeDefinition] enum En ... ember } | -| type_definitions.ts:18:1:18:33 | [EnumDeclaration,TypeDefinition] enum En ... ember } | semmle.order | 83 | +| type_definitions.ts:18:1:18:33 | [EnumDeclaration,TypeDefinition] enum En ... ember } | semmle.order | 111 | | type_definitions.ts:18:6:18:22 | [VarDecl] EnumWithOneMember | semmle.label | [VarDecl] EnumWithOneMember | | type_definitions.ts:18:26:18:31 | [EnumMember,TypeDefinition] member | semmle.label | [EnumMember,TypeDefinition] member | | type_definitions.ts:18:26:18:31 | [VarDecl] member | semmle.label | [VarDecl] member | | type_definitions.ts:19:1:19:25 | [DeclStmt] let e = ... | semmle.label | [DeclStmt] let e = ... | -| type_definitions.ts:19:1:19:25 | [DeclStmt] let e = ... | semmle.order | 84 | +| type_definitions.ts:19:1:19:25 | [DeclStmt] let e = ... | semmle.order | 112 | | type_definitions.ts:19:5:19:5 | [VarDecl] e | semmle.label | [VarDecl] e | | type_definitions.ts:19:5:19:24 | [VariableDeclarator] e: EnumWithOneMember | semmle.label | [VariableDeclarator] e: EnumWithOneMember | | type_definitions.ts:19:8:19:24 | [LocalTypeAccess] EnumWithOneMember | semmle.label | [LocalTypeAccess] EnumWithOneMember | | type_definitions.ts:21:1:21:20 | [TypeAliasDeclaration,TypeDefinition] type Alias = T[]; | semmle.label | [TypeAliasDeclaration,TypeDefinition] type Alias = T[]; | -| type_definitions.ts:21:1:21:20 | [TypeAliasDeclaration,TypeDefinition] type Alias = T[]; | semmle.order | 85 | +| type_definitions.ts:21:1:21:20 | [TypeAliasDeclaration,TypeDefinition] type Alias = T[]; | semmle.order | 113 | | type_definitions.ts:21:6:21:10 | [Identifier] Alias | semmle.label | [Identifier] Alias | | type_definitions.ts:21:12:21:12 | [Identifier] T | semmle.label | [Identifier] T | | type_definitions.ts:21:12:21:12 | [TypeParameter] T | semmle.label | [TypeParameter] T | | type_definitions.ts:21:17:21:17 | [LocalTypeAccess] T | semmle.label | [LocalTypeAccess] T | | type_definitions.ts:21:17:21:19 | [ArrayTypeExpr] T[] | semmle.label | [ArrayTypeExpr] T[] | | type_definitions.ts:22:1:22:39 | [DeclStmt] let aliasForNumberArray = ... | semmle.label | [DeclStmt] let aliasForNumberArray = ... | -| type_definitions.ts:22:1:22:39 | [DeclStmt] let aliasForNumberArray = ... | semmle.order | 86 | +| type_definitions.ts:22:1:22:39 | [DeclStmt] let aliasForNumberArray = ... | semmle.order | 114 | | type_definitions.ts:22:5:22:23 | [VarDecl] aliasForNumberArray | semmle.label | [VarDecl] aliasForNumberArray | | type_definitions.ts:22:5:22:38 | [VariableDeclarator] aliasFo ... number> | semmle.label | [VariableDeclarator] aliasFo ... number> | | type_definitions.ts:22:26:22:30 | [LocalTypeAccess] Alias | semmle.label | [LocalTypeAccess] Alias | @@ -1471,6 +1784,16 @@ edges | file://:0:0:0:0 | (Arguments) | tst.ts:282:17:287:3 | [ObjectExpr] {kind: ...} | semmle.order | 0 | | file://:0:0:0:0 | (Arguments) | tst.ts:285:19:285:35 | [MethodCallExpr] val.toUpperCase() | semmle.label | 0 | | file://:0:0:0:0 | (Arguments) | tst.ts:285:19:285:35 | [MethodCallExpr] val.toUpperCase() | semmle.order | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:318:3:321:1 | [ObjectExpr] {produce: ...} | semmle.label | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:318:3:321:1 | [ObjectExpr] {produce: ...} | semmle.order | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:358:13:358:25 | [CallExpr] tstModuleES() | semmle.label | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:358:13:358:25 | [CallExpr] tstModuleES() | semmle.order | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:362:13:362:26 | [CallExpr] tstModuleCJS() | semmle.label | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:362:13:362:26 | [CallExpr] tstModuleCJS() | semmle.order | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:370:13:370:28 | [MethodCallExpr] A.resolvedFile() | semmle.label | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:370:13:370:28 | [MethodCallExpr] A.resolvedFile() | semmle.order | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:374:13:374:28 | [MethodCallExpr] B.resolvedFile() | semmle.label | 0 | +| file://:0:0:0:0 | (Arguments) | tst.ts:374:13:374:28 | [MethodCallExpr] B.resolvedFile() | semmle.order | 0 | | file://:0:0:0:0 | (Parameters) | tst.ts:14:17:14:17 | [SimpleParameter] x | semmle.label | 0 | | file://:0:0:0:0 | (Parameters) | tst.ts:14:17:14:17 | [SimpleParameter] x | semmle.order | 0 | | file://:0:0:0:0 | (Parameters) | tst.ts:14:28:14:28 | [SimpleParameter] y | semmle.label | 1 | @@ -1529,6 +1852,20 @@ edges | file://:0:0:0:0 | (Parameters) | tst.ts:291:21:291:24 | [SimpleParameter] kind | semmle.order | 0 | | file://:0:0:0:0 | (Parameters) | tst.ts:291:27:291:33 | [SimpleParameter] payload | semmle.label | 1 | | file://:0:0:0:0 | (Parameters) | tst.ts:291:27:291:33 | [SimpleParameter] payload | semmle.order | 1 | +| file://:0:0:0:0 | (Parameters) | tst.ts:313:15:313:17 | [SimpleParameter] arg | semmle.label | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:313:15:313:17 | [SimpleParameter] arg | semmle.order | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:314:13:314:13 | [SimpleParameter] n | semmle.label | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:314:13:314:13 | [SimpleParameter] n | semmle.order | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:315:13:315:13 | [SimpleParameter] x | semmle.label | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:315:13:315:13 | [SimpleParameter] x | semmle.order | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:319:12:319:12 | [SimpleParameter] n | semmle.label | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:319:12:319:12 | [SimpleParameter] n | semmle.order | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:320:12:320:12 | [SimpleParameter] x | semmle.label | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:320:12:320:12 | [SimpleParameter] x | semmle.order | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:344:9:344:13 | [SimpleParameter] value | semmle.label | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:344:9:344:13 | [SimpleParameter] value | semmle.order | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:349:9:349:13 | [SimpleParameter] value | semmle.label | 0 | +| file://:0:0:0:0 | (Parameters) | tst.ts:349:9:349:13 | [SimpleParameter] value | semmle.order | 0 | | file://:0:0:0:0 | (Parameters) | type_alias.ts:14:10:14:17 | [SimpleParameter] property | semmle.label | 0 | | file://:0:0:0:0 | (Parameters) | type_alias.ts:14:10:14:17 | [SimpleParameter] property | semmle.order | 0 | | file://:0:0:0:0 | (Parameters) | type_alias.ts:21:19:21:21 | [SimpleParameter] key | semmle.label | 0 | @@ -1539,6 +1876,14 @@ edges | file://:0:0:0:0 | (TypeParameters) | tst.ts:272:6:272:11 | [TypeParameter] K in P | semmle.order | 0 | | file://:0:0:0:0 | (TypeParameters) | tst.ts:278:26:278:48 | [TypeParameter] K exten ... TypeMap | semmle.label | 0 | | file://:0:0:0:0 | (TypeParameters) | tst.ts:278:26:278:48 | [TypeParameter] K exten ... TypeMap | semmle.order | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:313:12:313:12 | [TypeParameter] T | semmle.label | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:313:12:313:12 | [TypeParameter] T | semmle.order | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:331:18:331:18 | [TypeParameter] T | semmle.label | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:331:18:331:18 | [TypeParameter] T | semmle.order | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:332:20:332:35 | [TypeParameter] S extends string | semmle.label | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:332:20:332:35 | [TypeParameter] S extends string | semmle.order | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:342:17:342:24 | [TypeParameter] in out T | semmle.label | 0 | +| file://:0:0:0:0 | (TypeParameters) | tst.ts:342:17:342:24 | [TypeParameter] in out T | semmle.order | 0 | | file://:0:0:0:0 | (TypeParameters) | type_alias.ts:5:19:5:19 | [TypeParameter] T | semmle.label | 0 | | file://:0:0:0:0 | (TypeParameters) | type_alias.ts:5:19:5:19 | [TypeParameter] T | semmle.order | 0 | | file://:0:0:0:0 | (TypeParameters) | type_definitions.ts:3:13:3:13 | [TypeParameter] S | semmle.label | 0 | @@ -3351,6 +3696,488 @@ edges | tst.ts:293:7:293:23 | [MethodCallExpr] payload.toFixed() | tst.ts:293:7:293:21 | [DotExpr] payload.toFixed | semmle.order | 0 | | tst.ts:293:7:293:24 | [ExprStmt] payload.toFixed(); | tst.ts:293:7:293:23 | [MethodCallExpr] payload.toFixed() | semmle.label | 1 | | tst.ts:293:7:293:24 | [ExprStmt] payload.toFixed(); | tst.ts:293:7:293:23 | [MethodCallExpr] payload.toFixed() | semmle.order | 1 | +| tst.ts:298:1:298:21 | [DeclStmt] const key = ... | tst.ts:298:7:298:20 | [VariableDeclarator] key = Symbol() | semmle.label | 1 | +| tst.ts:298:1:298:21 | [DeclStmt] const key = ... | tst.ts:298:7:298:20 | [VariableDeclarator] key = Symbol() | semmle.order | 1 | +| tst.ts:298:7:298:20 | [VariableDeclarator] key = Symbol() | tst.ts:298:7:298:9 | [VarDecl] key | semmle.label | 1 | +| tst.ts:298:7:298:20 | [VariableDeclarator] key = Symbol() | tst.ts:298:7:298:9 | [VarDecl] key | semmle.order | 1 | +| tst.ts:298:7:298:20 | [VariableDeclarator] key = Symbol() | tst.ts:298:13:298:20 | [CallExpr] Symbol() | semmle.label | 2 | +| tst.ts:298:7:298:20 | [VariableDeclarator] key = Symbol() | tst.ts:298:13:298:20 | [CallExpr] Symbol() | semmle.order | 2 | +| tst.ts:298:13:298:20 | [CallExpr] Symbol() | tst.ts:298:13:298:18 | [VarRef] Symbol | semmle.label | 0 | +| tst.ts:298:13:298:20 | [CallExpr] Symbol() | tst.ts:298:13:298:18 | [VarRef] Symbol | semmle.order | 0 | +| tst.ts:300:1:300:58 | [DeclStmt] const numberOrString = ... | tst.ts:300:7:300:57 | [VariableDeclarator] numberO ... "hello" | semmle.label | 1 | +| tst.ts:300:1:300:58 | [DeclStmt] const numberOrString = ... | tst.ts:300:7:300:57 | [VariableDeclarator] numberO ... "hello" | semmle.order | 1 | +| tst.ts:300:7:300:57 | [VariableDeclarator] numberO ... "hello" | tst.ts:300:7:300:20 | [VarDecl] numberOrString | semmle.label | 1 | +| tst.ts:300:7:300:57 | [VariableDeclarator] numberO ... "hello" | tst.ts:300:7:300:20 | [VarDecl] numberOrString | semmle.order | 1 | +| tst.ts:300:7:300:57 | [VariableDeclarator] numberO ... "hello" | tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | semmle.label | 2 | +| tst.ts:300:7:300:57 | [VariableDeclarator] numberO ... "hello" | tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | semmle.order | 2 | +| tst.ts:300:24:300:34 | [DotExpr] Math.random | tst.ts:300:24:300:27 | [VarRef] Math | semmle.label | 1 | +| tst.ts:300:24:300:34 | [DotExpr] Math.random | tst.ts:300:24:300:27 | [VarRef] Math | semmle.order | 1 | +| tst.ts:300:24:300:34 | [DotExpr] Math.random | tst.ts:300:29:300:34 | [Label] random | semmle.label | 2 | +| tst.ts:300:24:300:34 | [DotExpr] Math.random | tst.ts:300:29:300:34 | [Label] random | semmle.order | 2 | +| tst.ts:300:24:300:36 | [MethodCallExpr] Math.random() | tst.ts:300:24:300:34 | [DotExpr] Math.random | semmle.label | 0 | +| tst.ts:300:24:300:36 | [MethodCallExpr] Math.random() | tst.ts:300:24:300:34 | [DotExpr] Math.random | semmle.order | 0 | +| tst.ts:300:24:300:42 | [BinaryExpr] Math.random() < 0.5 | tst.ts:300:24:300:36 | [MethodCallExpr] Math.random() | semmle.label | 1 | +| tst.ts:300:24:300:42 | [BinaryExpr] Math.random() < 0.5 | tst.ts:300:24:300:36 | [MethodCallExpr] Math.random() | semmle.order | 1 | +| tst.ts:300:24:300:42 | [BinaryExpr] Math.random() < 0.5 | tst.ts:300:40:300:42 | [Literal] 0.5 | semmle.label | 2 | +| tst.ts:300:24:300:42 | [BinaryExpr] Math.random() < 0.5 | tst.ts:300:40:300:42 | [Literal] 0.5 | semmle.order | 2 | +| tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | tst.ts:300:24:300:42 | [BinaryExpr] Math.random() < 0.5 | semmle.label | 1 | +| tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | tst.ts:300:24:300:42 | [BinaryExpr] Math.random() < 0.5 | semmle.order | 1 | +| tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | tst.ts:300:46:300:47 | [Literal] 42 | semmle.label | 2 | +| tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | tst.ts:300:46:300:47 | [Literal] 42 | semmle.order | 2 | +| tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | tst.ts:300:51:300:57 | [Literal] "hello" | semmle.label | 3 | +| tst.ts:300:24:300:57 | [ConditionalExpr] Math.ra ... "hello" | tst.ts:300:51:300:57 | [Literal] "hello" | semmle.order | 3 | +| tst.ts:302:1:304:2 | [DeclStmt] let obj = ... | tst.ts:302:5:304:1 | [VariableDeclarator] obj = { ... ring, } | semmle.label | 1 | +| tst.ts:302:1:304:2 | [DeclStmt] let obj = ... | tst.ts:302:5:304:1 | [VariableDeclarator] obj = { ... ring, } | semmle.order | 1 | +| tst.ts:302:5:304:1 | [VariableDeclarator] obj = { ... ring, } | tst.ts:302:5:302:7 | [VarDecl] obj | semmle.label | 1 | +| tst.ts:302:5:304:1 | [VariableDeclarator] obj = { ... ring, } | tst.ts:302:5:302:7 | [VarDecl] obj | semmle.order | 1 | +| tst.ts:302:5:304:1 | [VariableDeclarator] obj = { ... ring, } | tst.ts:302:11:304:1 | [ObjectExpr] { [ke ... ring, } | semmle.label | 2 | +| tst.ts:302:5:304:1 | [VariableDeclarator] obj = { ... ring, } | tst.ts:302:11:304:1 | [ObjectExpr] { [ke ... ring, } | semmle.order | 2 | +| tst.ts:302:11:304:1 | [ObjectExpr] { [ke ... ring, } | tst.ts:303:3:303:23 | [Property] [key]: ... rString | semmle.label | 1 | +| tst.ts:302:11:304:1 | [ObjectExpr] { [ke ... ring, } | tst.ts:303:3:303:23 | [Property] [key]: ... rString | semmle.order | 1 | +| tst.ts:303:3:303:23 | [Property] [key]: ... rString | tst.ts:303:4:303:6 | [VarRef] key | semmle.label | 1 | +| tst.ts:303:3:303:23 | [Property] [key]: ... rString | tst.ts:303:4:303:6 | [VarRef] key | semmle.order | 1 | +| tst.ts:303:3:303:23 | [Property] [key]: ... rString | tst.ts:303:10:303:23 | [VarRef] numberOrString | semmle.label | 2 | +| tst.ts:303:3:303:23 | [Property] [key]: ... rString | tst.ts:303:10:303:23 | [VarRef] numberOrString | semmle.order | 2 | +| tst.ts:306:1:309:1 | [IfStmt] if (typ ... se(); } | tst.ts:306:5:306:32 | [BinaryExpr] typeof ... string" | semmle.label | 1 | +| tst.ts:306:1:309:1 | [IfStmt] if (typ ... se(); } | tst.ts:306:5:306:32 | [BinaryExpr] typeof ... string" | semmle.order | 1 | +| tst.ts:306:1:309:1 | [IfStmt] if (typ ... se(); } | tst.ts:306:35:309:1 | [BlockStmt] { let ... se(); } | semmle.label | 2 | +| tst.ts:306:1:309:1 | [IfStmt] if (typ ... se(); } | tst.ts:306:35:309:1 | [BlockStmt] { let ... se(); } | semmle.order | 2 | +| tst.ts:306:5:306:19 | [UnaryExpr] typeof obj[key] | tst.ts:306:12:306:19 | [IndexExpr] obj[key] | semmle.label | 1 | +| tst.ts:306:5:306:19 | [UnaryExpr] typeof obj[key] | tst.ts:306:12:306:19 | [IndexExpr] obj[key] | semmle.order | 1 | +| tst.ts:306:5:306:32 | [BinaryExpr] typeof ... string" | tst.ts:306:5:306:19 | [UnaryExpr] typeof obj[key] | semmle.label | 1 | +| tst.ts:306:5:306:32 | [BinaryExpr] typeof ... string" | tst.ts:306:5:306:19 | [UnaryExpr] typeof obj[key] | semmle.order | 1 | +| tst.ts:306:5:306:32 | [BinaryExpr] typeof ... string" | tst.ts:306:25:306:32 | [Literal] "string" | semmle.label | 2 | +| tst.ts:306:5:306:32 | [BinaryExpr] typeof ... string" | tst.ts:306:25:306:32 | [Literal] "string" | semmle.order | 2 | +| tst.ts:306:12:306:19 | [IndexExpr] obj[key] | tst.ts:306:12:306:14 | [VarRef] obj | semmle.label | 1 | +| tst.ts:306:12:306:19 | [IndexExpr] obj[key] | tst.ts:306:12:306:14 | [VarRef] obj | semmle.order | 1 | +| tst.ts:306:12:306:19 | [IndexExpr] obj[key] | tst.ts:306:16:306:18 | [VarRef] key | semmle.label | 2 | +| tst.ts:306:12:306:19 | [IndexExpr] obj[key] | tst.ts:306:16:306:18 | [VarRef] key | semmle.order | 2 | +| tst.ts:306:35:309:1 | [BlockStmt] { let ... se(); } | tst.ts:307:3:307:21 | [DeclStmt] let str = ... | semmle.label | 1 | +| tst.ts:306:35:309:1 | [BlockStmt] { let ... se(); } | tst.ts:307:3:307:21 | [DeclStmt] let str = ... | semmle.order | 1 | +| tst.ts:306:35:309:1 | [BlockStmt] { let ... se(); } | tst.ts:308:3:308:20 | [ExprStmt] str.toUpperCase(); | semmle.label | 2 | +| tst.ts:306:35:309:1 | [BlockStmt] { let ... se(); } | tst.ts:308:3:308:20 | [ExprStmt] str.toUpperCase(); | semmle.order | 2 | +| tst.ts:307:3:307:21 | [DeclStmt] let str = ... | tst.ts:307:7:307:20 | [VariableDeclarator] str = obj[key] | semmle.label | 1 | +| tst.ts:307:3:307:21 | [DeclStmt] let str = ... | tst.ts:307:7:307:20 | [VariableDeclarator] str = obj[key] | semmle.order | 1 | +| tst.ts:307:7:307:20 | [VariableDeclarator] str = obj[key] | tst.ts:307:7:307:9 | [VarDecl] str | semmle.label | 1 | +| tst.ts:307:7:307:20 | [VariableDeclarator] str = obj[key] | tst.ts:307:7:307:9 | [VarDecl] str | semmle.order | 1 | +| tst.ts:307:7:307:20 | [VariableDeclarator] str = obj[key] | tst.ts:307:13:307:20 | [IndexExpr] obj[key] | semmle.label | 2 | +| tst.ts:307:7:307:20 | [VariableDeclarator] str = obj[key] | tst.ts:307:13:307:20 | [IndexExpr] obj[key] | semmle.order | 2 | +| tst.ts:307:13:307:20 | [IndexExpr] obj[key] | tst.ts:307:13:307:15 | [VarRef] obj | semmle.label | 1 | +| tst.ts:307:13:307:20 | [IndexExpr] obj[key] | tst.ts:307:13:307:15 | [VarRef] obj | semmle.order | 1 | +| tst.ts:307:13:307:20 | [IndexExpr] obj[key] | tst.ts:307:17:307:19 | [VarRef] key | semmle.label | 2 | +| tst.ts:307:13:307:20 | [IndexExpr] obj[key] | tst.ts:307:17:307:19 | [VarRef] key | semmle.order | 2 | +| tst.ts:308:3:308:17 | [DotExpr] str.toUpperCase | tst.ts:308:3:308:5 | [VarRef] str | semmle.label | 1 | +| tst.ts:308:3:308:17 | [DotExpr] str.toUpperCase | tst.ts:308:3:308:5 | [VarRef] str | semmle.order | 1 | +| tst.ts:308:3:308:17 | [DotExpr] str.toUpperCase | tst.ts:308:7:308:17 | [Label] toUpperCase | semmle.label | 2 | +| tst.ts:308:3:308:17 | [DotExpr] str.toUpperCase | tst.ts:308:7:308:17 | [Label] toUpperCase | semmle.order | 2 | +| tst.ts:308:3:308:19 | [MethodCallExpr] str.toUpperCase() | tst.ts:308:3:308:17 | [DotExpr] str.toUpperCase | semmle.label | 0 | +| tst.ts:308:3:308:19 | [MethodCallExpr] str.toUpperCase() | tst.ts:308:3:308:17 | [DotExpr] str.toUpperCase | semmle.order | 0 | +| tst.ts:308:3:308:20 | [ExprStmt] str.toUpperCase(); | tst.ts:308:3:308:19 | [MethodCallExpr] str.toUpperCase() | semmle.label | 1 | +| tst.ts:308:3:308:20 | [ExprStmt] str.toUpperCase(); | tst.ts:308:3:308:19 | [MethodCallExpr] str.toUpperCase() | semmle.order | 1 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | file://:0:0:0:0 | (Parameters) | semmle.label | 1 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | file://:0:0:0:0 | (Parameters) | semmle.order | 1 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | file://:0:0:0:0 | (TypeParameters) | semmle.label | 2 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | file://:0:0:0:0 | (TypeParameters) | semmle.order | 2 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | tst.ts:313:10:313:10 | [VarDecl] f | semmle.label | 0 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | tst.ts:313:10:313:10 | [VarDecl] f | semmle.order | 0 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | tst.ts:316:4:316:7 | [KeywordTypeExpr] void | semmle.label | 4 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | tst.ts:316:4:316:7 | [KeywordTypeExpr] void | semmle.order | 4 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | tst.ts:316:9:316:10 | [BlockStmt] {} | semmle.label | 5 | +| tst.ts:313:1:316:10 | [FunctionDeclStmt] functio ... void {} | tst.ts:316:9:316:10 | [BlockStmt] {} | semmle.order | 5 | +| tst.ts:313:12:313:12 | [TypeParameter] T | tst.ts:313:12:313:12 | [Identifier] T | semmle.label | 1 | +| tst.ts:313:12:313:12 | [TypeParameter] T | tst.ts:313:12:313:12 | [Identifier] T | semmle.order | 1 | +| tst.ts:313:15:313:17 | [SimpleParameter] arg | tst.ts:313:20:315:27 | [InterfaceTypeExpr] { pro ... void } | semmle.label | 0 | +| tst.ts:313:15:313:17 | [SimpleParameter] arg | tst.ts:313:20:315:27 | [InterfaceTypeExpr] { pro ... void } | semmle.order | 0 | +| tst.ts:313:20:315:27 | [InterfaceTypeExpr] { pro ... void } | tst.ts:314:3:314:28 | [FieldDeclaration] produce ... ) => T, | semmle.label | 1 | +| tst.ts:313:20:315:27 | [InterfaceTypeExpr] { pro ... void } | tst.ts:314:3:314:28 | [FieldDeclaration] produce ... ) => T, | semmle.order | 1 | +| tst.ts:313:20:315:27 | [InterfaceTypeExpr] { pro ... void } | tst.ts:315:3:315:25 | [FieldDeclaration] consume ... => void | semmle.label | 2 | +| tst.ts:313:20:315:27 | [InterfaceTypeExpr] { pro ... void } | tst.ts:315:3:315:25 | [FieldDeclaration] consume ... => void | semmle.order | 2 | +| tst.ts:314:3:314:28 | [FieldDeclaration] produce ... ) => T, | tst.ts:314:3:314:9 | [Label] produce | semmle.label | 1 | +| tst.ts:314:3:314:28 | [FieldDeclaration] produce ... ) => T, | tst.ts:314:3:314:9 | [Label] produce | semmle.order | 1 | +| tst.ts:314:3:314:28 | [FieldDeclaration] produce ... ) => T, | tst.ts:314:12:314:27 | [FunctionTypeExpr] (n: string) => T | semmle.label | 2 | +| tst.ts:314:3:314:28 | [FieldDeclaration] produce ... ) => T, | tst.ts:314:12:314:27 | [FunctionTypeExpr] (n: string) => T | semmle.order | 2 | +| tst.ts:314:12:314:27 | [FunctionExpr] (n: string) => T | file://:0:0:0:0 | (Parameters) | semmle.label | 1 | +| tst.ts:314:12:314:27 | [FunctionExpr] (n: string) => T | file://:0:0:0:0 | (Parameters) | semmle.order | 1 | +| tst.ts:314:12:314:27 | [FunctionExpr] (n: string) => T | tst.ts:314:27:314:27 | [LocalTypeAccess] T | semmle.label | 4 | +| tst.ts:314:12:314:27 | [FunctionExpr] (n: string) => T | tst.ts:314:27:314:27 | [LocalTypeAccess] T | semmle.order | 4 | +| tst.ts:314:12:314:27 | [FunctionTypeExpr] (n: string) => T | tst.ts:314:12:314:27 | [FunctionExpr] (n: string) => T | semmle.label | 1 | +| tst.ts:314:12:314:27 | [FunctionTypeExpr] (n: string) => T | tst.ts:314:12:314:27 | [FunctionExpr] (n: string) => T | semmle.order | 1 | +| tst.ts:314:13:314:13 | [SimpleParameter] n | tst.ts:314:16:314:21 | [KeywordTypeExpr] string | semmle.label | 0 | +| tst.ts:314:13:314:13 | [SimpleParameter] n | tst.ts:314:16:314:21 | [KeywordTypeExpr] string | semmle.order | 0 | +| tst.ts:315:3:315:25 | [FieldDeclaration] consume ... => void | tst.ts:315:3:315:9 | [Label] consume | semmle.label | 1 | +| tst.ts:315:3:315:25 | [FieldDeclaration] consume ... => void | tst.ts:315:3:315:9 | [Label] consume | semmle.order | 1 | +| tst.ts:315:3:315:25 | [FieldDeclaration] consume ... => void | tst.ts:315:12:315:25 | [FunctionTypeExpr] (x: T) => void | semmle.label | 2 | +| tst.ts:315:3:315:25 | [FieldDeclaration] consume ... => void | tst.ts:315:12:315:25 | [FunctionTypeExpr] (x: T) => void | semmle.order | 2 | +| tst.ts:315:12:315:25 | [FunctionExpr] (x: T) => void | file://:0:0:0:0 | (Parameters) | semmle.label | 1 | +| tst.ts:315:12:315:25 | [FunctionExpr] (x: T) => void | file://:0:0:0:0 | (Parameters) | semmle.order | 1 | +| tst.ts:315:12:315:25 | [FunctionExpr] (x: T) => void | tst.ts:315:22:315:25 | [KeywordTypeExpr] void | semmle.label | 4 | +| tst.ts:315:12:315:25 | [FunctionExpr] (x: T) => void | tst.ts:315:22:315:25 | [KeywordTypeExpr] void | semmle.order | 4 | +| tst.ts:315:12:315:25 | [FunctionTypeExpr] (x: T) => void | tst.ts:315:12:315:25 | [FunctionExpr] (x: T) => void | semmle.label | 1 | +| tst.ts:315:12:315:25 | [FunctionTypeExpr] (x: T) => void | tst.ts:315:12:315:25 | [FunctionExpr] (x: T) => void | semmle.order | 1 | +| tst.ts:315:13:315:13 | [SimpleParameter] x | tst.ts:315:16:315:16 | [LocalTypeAccess] T | semmle.label | 0 | +| tst.ts:315:13:315:13 | [SimpleParameter] x | tst.ts:315:16:315:16 | [LocalTypeAccess] T | semmle.order | 0 | +| tst.ts:318:1:321:2 | [CallExpr] f({ p ... se() }) | file://:0:0:0:0 | (Arguments) | semmle.label | 1 | +| tst.ts:318:1:321:2 | [CallExpr] f({ p ... se() }) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | +| tst.ts:318:1:321:2 | [CallExpr] f({ p ... se() }) | tst.ts:318:1:318:1 | [VarRef] f | semmle.label | 0 | +| tst.ts:318:1:321:2 | [CallExpr] f({ p ... se() }) | tst.ts:318:1:318:1 | [VarRef] f | semmle.order | 0 | +| tst.ts:318:1:321:3 | [ExprStmt] f({ p ... e() }); | tst.ts:318:1:321:2 | [CallExpr] f({ p ... se() }) | semmle.label | 1 | +| tst.ts:318:1:321:3 | [ExprStmt] f({ p ... e() }); | tst.ts:318:1:321:2 | [CallExpr] f({ p ... se() }) | semmle.order | 1 | +| tst.ts:318:3:321:1 | [ObjectExpr] {produce: ...} | tst.ts:319:3:319:17 | [Property] produce: n => n | semmle.label | 1 | +| tst.ts:318:3:321:1 | [ObjectExpr] {produce: ...} | tst.ts:319:3:319:17 | [Property] produce: n => n | semmle.order | 1 | +| tst.ts:318:3:321:1 | [ObjectExpr] {produce: ...} | tst.ts:320:3:320:31 | [Property] consume ... rCase() | semmle.label | 2 | +| tst.ts:318:3:321:1 | [ObjectExpr] {produce: ...} | tst.ts:320:3:320:31 | [Property] consume ... rCase() | semmle.order | 2 | +| tst.ts:319:3:319:17 | [Property] produce: n => n | tst.ts:319:3:319:9 | [Label] produce | semmle.label | 1 | +| tst.ts:319:3:319:17 | [Property] produce: n => n | tst.ts:319:3:319:9 | [Label] produce | semmle.order | 1 | +| tst.ts:319:3:319:17 | [Property] produce: n => n | tst.ts:319:12:319:17 | [ArrowFunctionExpr] n => n | semmle.label | 2 | +| tst.ts:319:3:319:17 | [Property] produce: n => n | tst.ts:319:12:319:17 | [ArrowFunctionExpr] n => n | semmle.order | 2 | +| tst.ts:319:12:319:17 | [ArrowFunctionExpr] n => n | file://:0:0:0:0 | (Parameters) | semmle.label | 1 | +| tst.ts:319:12:319:17 | [ArrowFunctionExpr] n => n | file://:0:0:0:0 | (Parameters) | semmle.order | 1 | +| tst.ts:319:12:319:17 | [ArrowFunctionExpr] n => n | tst.ts:319:17:319:17 | [VarRef] n | semmle.label | 5 | +| tst.ts:319:12:319:17 | [ArrowFunctionExpr] n => n | tst.ts:319:17:319:17 | [VarRef] n | semmle.order | 5 | +| tst.ts:320:3:320:31 | [Property] consume ... rCase() | tst.ts:320:3:320:9 | [Label] consume | semmle.label | 1 | +| tst.ts:320:3:320:31 | [Property] consume ... rCase() | tst.ts:320:3:320:9 | [Label] consume | semmle.order | 1 | +| tst.ts:320:3:320:31 | [Property] consume ... rCase() | tst.ts:320:12:320:31 | [ArrowFunctionExpr] x => x.toLowerCase() | semmle.label | 2 | +| tst.ts:320:3:320:31 | [Property] consume ... rCase() | tst.ts:320:12:320:31 | [ArrowFunctionExpr] x => x.toLowerCase() | semmle.order | 2 | +| tst.ts:320:12:320:31 | [ArrowFunctionExpr] x => x.toLowerCase() | file://:0:0:0:0 | (Parameters) | semmle.label | 1 | +| tst.ts:320:12:320:31 | [ArrowFunctionExpr] x => x.toLowerCase() | file://:0:0:0:0 | (Parameters) | semmle.order | 1 | +| tst.ts:320:12:320:31 | [ArrowFunctionExpr] x => x.toLowerCase() | tst.ts:320:17:320:31 | [MethodCallExpr] x.toLowerCase() | semmle.label | 5 | +| tst.ts:320:12:320:31 | [ArrowFunctionExpr] x => x.toLowerCase() | tst.ts:320:17:320:31 | [MethodCallExpr] x.toLowerCase() | semmle.order | 5 | +| tst.ts:320:17:320:29 | [DotExpr] x.toLowerCase | tst.ts:320:17:320:17 | [VarRef] x | semmle.label | 1 | +| tst.ts:320:17:320:29 | [DotExpr] x.toLowerCase | tst.ts:320:17:320:17 | [VarRef] x | semmle.order | 1 | +| tst.ts:320:17:320:29 | [DotExpr] x.toLowerCase | tst.ts:320:19:320:29 | [Label] toLowerCase | semmle.label | 2 | +| tst.ts:320:17:320:29 | [DotExpr] x.toLowerCase | tst.ts:320:19:320:29 | [Label] toLowerCase | semmle.order | 2 | +| tst.ts:320:17:320:31 | [MethodCallExpr] x.toLowerCase() | tst.ts:320:17:320:29 | [DotExpr] x.toLowerCase | semmle.label | 0 | +| tst.ts:320:17:320:31 | [MethodCallExpr] x.toLowerCase() | tst.ts:320:17:320:29 | [DotExpr] x.toLowerCase | semmle.order | 0 | +| tst.ts:325:1:325:36 | [DeclStmt] const ErrorMap = ... | tst.ts:325:7:325:35 | [VariableDeclarator] ErrorMa ... Error> | semmle.label | 1 | +| tst.ts:325:1:325:36 | [DeclStmt] const ErrorMap = ... | tst.ts:325:7:325:35 | [VariableDeclarator] ErrorMa ... Error> | semmle.order | 1 | +| tst.ts:325:7:325:35 | [VariableDeclarator] ErrorMa ... Error> | tst.ts:325:7:325:14 | [VarDecl] ErrorMap | semmle.label | 1 | +| tst.ts:325:7:325:35 | [VariableDeclarator] ErrorMa ... Error> | tst.ts:325:7:325:14 | [VarDecl] ErrorMap | semmle.order | 1 | +| tst.ts:325:7:325:35 | [VariableDeclarator] ErrorMa ... Error> | tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | semmle.label | 2 | +| tst.ts:325:7:325:35 | [VariableDeclarator] ErrorMa ... Error> | tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | semmle.order | 2 | +| tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | tst.ts:325:18:325:20 | [VarRef] Map | semmle.label | 1 | +| tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | tst.ts:325:18:325:20 | [VarRef] Map | semmle.order | 1 | +| tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | tst.ts:325:22:325:27 | [KeywordTypeExpr] string | semmle.label | 2 | +| tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | tst.ts:325:22:325:27 | [KeywordTypeExpr] string | semmle.order | 2 | +| tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | tst.ts:325:30:325:34 | [LocalTypeAccess] Error | semmle.label | 3 | +| tst.ts:325:18:325:35 | [ExpressionWithTypeArguments] Map | tst.ts:325:30:325:34 | [LocalTypeAccess] Error | semmle.order | 3 | +| tst.ts:327:1:327:32 | [DeclStmt] const errorMap = ... | tst.ts:327:7:327:31 | [VariableDeclarator] errorMa ... orMap() | semmle.label | 1 | +| tst.ts:327:1:327:32 | [DeclStmt] const errorMap = ... | tst.ts:327:7:327:31 | [VariableDeclarator] errorMa ... orMap() | semmle.order | 1 | +| tst.ts:327:7:327:31 | [VariableDeclarator] errorMa ... orMap() | tst.ts:327:7:327:14 | [VarDecl] errorMap | semmle.label | 1 | +| tst.ts:327:7:327:31 | [VariableDeclarator] errorMa ... orMap() | tst.ts:327:7:327:14 | [VarDecl] errorMap | semmle.order | 1 | +| tst.ts:327:7:327:31 | [VariableDeclarator] errorMa ... orMap() | tst.ts:327:18:327:31 | [NewExpr] new ErrorMap() | semmle.label | 2 | +| tst.ts:327:7:327:31 | [VariableDeclarator] errorMa ... orMap() | tst.ts:327:18:327:31 | [NewExpr] new ErrorMap() | semmle.order | 2 | +| tst.ts:327:18:327:31 | [NewExpr] new ErrorMap() | tst.ts:327:22:327:29 | [VarRef] ErrorMap | semmle.label | 0 | +| tst.ts:327:18:327:31 | [NewExpr] new ErrorMap() | tst.ts:327:22:327:29 | [VarRef] ErrorMap | semmle.order | 0 | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | file://:0:0:0:0 | (TypeParameters) | semmle.label | -100 | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | file://:0:0:0:0 | (TypeParameters) | semmle.order | -100 | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | tst.ts:331:6:331:16 | [Identifier] FirstString | semmle.label | 1 | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | tst.ts:331:6:331:16 | [Identifier] FirstString | semmle.order | 1 | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | semmle.label | 3 | +| tst.ts:331:1:334:14 | [TypeAliasDeclaration,TypeDefinition] type Fi ... never; | tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | semmle.order | 3 | +| tst.ts:331:18:331:18 | [TypeParameter] T | tst.ts:331:18:331:18 | [Identifier] T | semmle.label | 1 | +| tst.ts:331:18:331:18 | [TypeParameter] T | tst.ts:331:18:331:18 | [Identifier] T | semmle.order | 1 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:332:3:332:3 | [LocalTypeAccess] T | semmle.label | 1 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:332:3:332:3 | [LocalTypeAccess] T | semmle.order | 1 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:332:13:332:50 | [TupleTypeExpr] [infer ... nown[]] | semmle.label | 2 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:332:13:332:50 | [TupleTypeExpr] [infer ... nown[]] | semmle.order | 2 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:333:9:333:9 | [LocalTypeAccess] S | semmle.label | 3 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:333:9:333:9 | [LocalTypeAccess] S | semmle.order | 3 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:334:9:334:13 | [KeywordTypeExpr] never | semmle.label | 4 | +| tst.ts:332:3:334:13 | [ConditionalTypeExpr] T exten ... : never | tst.ts:334:9:334:13 | [KeywordTypeExpr] never | semmle.order | 4 | +| tst.ts:332:13:332:50 | [TupleTypeExpr] [infer ... nown[]] | tst.ts:332:14:332:35 | [InferTypeExpr] infer S ... string | semmle.label | 1 | +| tst.ts:332:13:332:50 | [TupleTypeExpr] [infer ... nown[]] | tst.ts:332:14:332:35 | [InferTypeExpr] infer S ... string | semmle.order | 1 | +| tst.ts:332:13:332:50 | [TupleTypeExpr] [infer ... nown[]] | tst.ts:332:38:332:49 | [RestTypeExpr] ...unknown[] | semmle.label | 2 | +| tst.ts:332:13:332:50 | [TupleTypeExpr] [infer ... nown[]] | tst.ts:332:38:332:49 | [RestTypeExpr] ...unknown[] | semmle.order | 2 | +| tst.ts:332:14:332:35 | [InferTypeExpr] infer S ... string | file://:0:0:0:0 | (TypeParameters) | semmle.label | -100 | +| tst.ts:332:14:332:35 | [InferTypeExpr] infer S ... string | file://:0:0:0:0 | (TypeParameters) | semmle.order | -100 | +| tst.ts:332:20:332:35 | [TypeParameter] S extends string | tst.ts:332:20:332:20 | [Identifier] S | semmle.label | 1 | +| tst.ts:332:20:332:35 | [TypeParameter] S extends string | tst.ts:332:20:332:20 | [Identifier] S | semmle.order | 1 | +| tst.ts:332:20:332:35 | [TypeParameter] S extends string | tst.ts:332:30:332:35 | [KeywordTypeExpr] string | semmle.label | 2 | +| tst.ts:332:20:332:35 | [TypeParameter] S extends string | tst.ts:332:30:332:35 | [KeywordTypeExpr] string | semmle.order | 2 | +| tst.ts:332:38:332:49 | [RestTypeExpr] ...unknown[] | tst.ts:332:41:332:49 | [ArrayTypeExpr] unknown[] | semmle.label | 1 | +| tst.ts:332:38:332:49 | [RestTypeExpr] ...unknown[] | tst.ts:332:41:332:49 | [ArrayTypeExpr] unknown[] | semmle.order | 1 | +| tst.ts:332:41:332:49 | [ArrayTypeExpr] unknown[] | tst.ts:332:41:332:47 | [KeywordTypeExpr] unknown | semmle.label | 1 | +| tst.ts:332:41:332:49 | [ArrayTypeExpr] unknown[] | tst.ts:332:41:332:47 | [KeywordTypeExpr] unknown | semmle.order | 1 | +| tst.ts:336:1:336:51 | [TypeAliasDeclaration,TypeDefinition] type F ... lean]>; | tst.ts:336:6:336:6 | [Identifier] F | semmle.label | 1 | +| tst.ts:336:1:336:51 | [TypeAliasDeclaration,TypeDefinition] type F ... lean]>; | tst.ts:336:6:336:6 | [Identifier] F | semmle.order | 1 | +| tst.ts:336:1:336:51 | [TypeAliasDeclaration,TypeDefinition] type F ... lean]>; | tst.ts:336:10:336:50 | [GenericTypeExpr] FirstSt ... olean]> | semmle.label | 2 | +| tst.ts:336:1:336:51 | [TypeAliasDeclaration,TypeDefinition] type F ... lean]>; | tst.ts:336:10:336:50 | [GenericTypeExpr] FirstSt ... olean]> | semmle.order | 2 | +| tst.ts:336:10:336:50 | [GenericTypeExpr] FirstSt ... olean]> | tst.ts:336:10:336:20 | [LocalTypeAccess] FirstString | semmle.label | 1 | +| tst.ts:336:10:336:50 | [GenericTypeExpr] FirstSt ... olean]> | tst.ts:336:10:336:20 | [LocalTypeAccess] FirstString | semmle.order | 1 | +| tst.ts:336:10:336:50 | [GenericTypeExpr] FirstSt ... olean]> | tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | semmle.label | 2 | +| tst.ts:336:10:336:50 | [GenericTypeExpr] FirstSt ... olean]> | tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | semmle.order | 2 | +| tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | tst.ts:336:23:336:31 | [UnionTypeExpr] 'a' \| 'b' | semmle.label | 1 | +| tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | tst.ts:336:23:336:31 | [UnionTypeExpr] 'a' \| 'b' | semmle.order | 1 | +| tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | tst.ts:336:34:336:39 | [KeywordTypeExpr] number | semmle.label | 2 | +| tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | tst.ts:336:34:336:39 | [KeywordTypeExpr] number | semmle.order | 2 | +| tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | tst.ts:336:42:336:48 | [KeywordTypeExpr] boolean | semmle.label | 3 | +| tst.ts:336:22:336:49 | [TupleTypeExpr] ['a' \| ... oolean] | tst.ts:336:42:336:48 | [KeywordTypeExpr] boolean | semmle.order | 3 | +| tst.ts:336:23:336:31 | [UnionTypeExpr] 'a' \| 'b' | tst.ts:336:23:336:25 | [LiteralTypeExpr] 'a' | semmle.label | 1 | +| tst.ts:336:23:336:31 | [UnionTypeExpr] 'a' \| 'b' | tst.ts:336:23:336:25 | [LiteralTypeExpr] 'a' | semmle.order | 1 | +| tst.ts:336:23:336:31 | [UnionTypeExpr] 'a' \| 'b' | tst.ts:336:29:336:31 | [LiteralTypeExpr] 'b' | semmle.label | 2 | +| tst.ts:336:23:336:31 | [UnionTypeExpr] 'a' \| 'b' | tst.ts:336:29:336:31 | [LiteralTypeExpr] 'b' | semmle.order | 2 | +| tst.ts:338:1:338:17 | [DeclStmt] const a = ... | tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | semmle.label | 1 | +| tst.ts:338:1:338:17 | [DeclStmt] const a = ... | tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | semmle.order | 1 | +| tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | tst.ts:338:7:338:7 | [VarDecl] a | semmle.label | 1 | +| tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | tst.ts:338:7:338:7 | [VarDecl] a | semmle.order | 1 | +| tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | tst.ts:338:10:338:10 | [LocalTypeAccess] F | semmle.label | 2 | +| tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | tst.ts:338:10:338:10 | [LocalTypeAccess] F | semmle.order | 2 | +| tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | tst.ts:338:14:338:16 | [Literal] 'a' | semmle.label | 3 | +| tst.ts:338:7:338:16 | [VariableDeclarator] a: F = 'a' | tst.ts:338:14:338:16 | [Literal] 'a' | semmle.order | 3 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | file://:0:0:0:0 | (TypeParameters) | semmle.label | -100 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | file://:0:0:0:0 | (TypeParameters) | semmle.order | -100 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | tst.ts:342:11:342:15 | [Identifier] State | semmle.label | 1 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | tst.ts:342:11:342:15 | [Identifier] State | semmle.order | 1 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | tst.ts:343:3:343:15 | [FieldDeclaration] get: () => T; | semmle.label | 3 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | tst.ts:343:3:343:15 | [FieldDeclaration] get: () => T; | semmle.order | 3 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | tst.ts:344:3:344:26 | [FieldDeclaration] set: (v ... > void; | semmle.label | 4 | +| tst.ts:342:1:345:1 | [InterfaceDeclaration,TypeDefinition] interfa ... void; } | tst.ts:344:3:344:26 | [FieldDeclaration] set: (v ... > void; | semmle.order | 4 | +| tst.ts:342:17:342:24 | [TypeParameter] in out T | tst.ts:342:24:342:24 | [Identifier] T | semmle.label | 1 | +| tst.ts:342:17:342:24 | [TypeParameter] in out T | tst.ts:342:24:342:24 | [Identifier] T | semmle.order | 1 | +| tst.ts:343:3:343:15 | [FieldDeclaration] get: () => T; | tst.ts:343:3:343:5 | [Label] get | semmle.label | 1 | +| tst.ts:343:3:343:15 | [FieldDeclaration] get: () => T; | tst.ts:343:3:343:5 | [Label] get | semmle.order | 1 | +| tst.ts:343:3:343:15 | [FieldDeclaration] get: () => T; | tst.ts:343:8:343:14 | [FunctionTypeExpr] () => T | semmle.label | 2 | +| tst.ts:343:3:343:15 | [FieldDeclaration] get: () => T; | tst.ts:343:8:343:14 | [FunctionTypeExpr] () => T | semmle.order | 2 | +| tst.ts:343:8:343:14 | [FunctionExpr] () => T | tst.ts:343:14:343:14 | [LocalTypeAccess] T | semmle.label | 4 | +| tst.ts:343:8:343:14 | [FunctionExpr] () => T | tst.ts:343:14:343:14 | [LocalTypeAccess] T | semmle.order | 4 | +| tst.ts:343:8:343:14 | [FunctionTypeExpr] () => T | tst.ts:343:8:343:14 | [FunctionExpr] () => T | semmle.label | 1 | +| tst.ts:343:8:343:14 | [FunctionTypeExpr] () => T | tst.ts:343:8:343:14 | [FunctionExpr] () => T | semmle.order | 1 | +| tst.ts:344:3:344:26 | [FieldDeclaration] set: (v ... > void; | tst.ts:344:3:344:5 | [Label] set | semmle.label | 1 | +| tst.ts:344:3:344:26 | [FieldDeclaration] set: (v ... > void; | tst.ts:344:3:344:5 | [Label] set | semmle.order | 1 | +| tst.ts:344:3:344:26 | [FieldDeclaration] set: (v ... > void; | tst.ts:344:8:344:25 | [FunctionTypeExpr] (value: T) => void | semmle.label | 2 | +| tst.ts:344:3:344:26 | [FieldDeclaration] set: (v ... > void; | tst.ts:344:8:344:25 | [FunctionTypeExpr] (value: T) => void | semmle.order | 2 | +| tst.ts:344:8:344:25 | [FunctionExpr] (value: T) => void | file://:0:0:0:0 | (Parameters) | semmle.label | 1 | +| tst.ts:344:8:344:25 | [FunctionExpr] (value: T) => void | file://:0:0:0:0 | (Parameters) | semmle.order | 1 | +| tst.ts:344:8:344:25 | [FunctionExpr] (value: T) => void | tst.ts:344:22:344:25 | [KeywordTypeExpr] void | semmle.label | 4 | +| tst.ts:344:8:344:25 | [FunctionExpr] (value: T) => void | tst.ts:344:22:344:25 | [KeywordTypeExpr] void | semmle.order | 4 | +| tst.ts:344:8:344:25 | [FunctionTypeExpr] (value: T) => void | tst.ts:344:8:344:25 | [FunctionExpr] (value: T) => void | semmle.label | 1 | +| tst.ts:344:8:344:25 | [FunctionTypeExpr] (value: T) => void | tst.ts:344:8:344:25 | [FunctionExpr] (value: T) => void | semmle.order | 1 | +| tst.ts:344:9:344:13 | [SimpleParameter] value | tst.ts:344:16:344:16 | [LocalTypeAccess] T | semmle.label | 0 | +| tst.ts:344:9:344:13 | [SimpleParameter] value | tst.ts:344:16:344:16 | [LocalTypeAccess] T | semmle.order | 0 | +| tst.ts:347:1:350:1 | [DeclStmt] const state = ... | tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | semmle.label | 1 | +| tst.ts:347:1:350:1 | [DeclStmt] const state = ... | tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | semmle.order | 1 | +| tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | tst.ts:347:7:347:11 | [VarDecl] state | semmle.label | 1 | +| tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | tst.ts:347:7:347:11 | [VarDecl] state | semmle.order | 1 | +| tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | tst.ts:347:14:347:26 | [GenericTypeExpr] State | semmle.label | 2 | +| tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | tst.ts:347:14:347:26 | [GenericTypeExpr] State | semmle.order | 2 | +| tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | tst.ts:347:30:350:1 | [ObjectExpr] {get: ...} | semmle.label | 3 | +| tst.ts:347:7:350:1 | [VariableDeclarator] state: ... > { } } | tst.ts:347:30:350:1 | [ObjectExpr] {get: ...} | semmle.order | 3 | +| tst.ts:347:14:347:26 | [GenericTypeExpr] State | tst.ts:347:14:347:18 | [LocalTypeAccess] State | semmle.label | 1 | +| tst.ts:347:14:347:26 | [GenericTypeExpr] State | tst.ts:347:14:347:18 | [LocalTypeAccess] State | semmle.order | 1 | +| tst.ts:347:14:347:26 | [GenericTypeExpr] State | tst.ts:347:20:347:25 | [KeywordTypeExpr] number | semmle.label | 2 | +| tst.ts:347:14:347:26 | [GenericTypeExpr] State | tst.ts:347:20:347:25 | [KeywordTypeExpr] number | semmle.order | 2 | +| tst.ts:347:30:350:1 | [ObjectExpr] {get: ...} | tst.ts:348:3:348:15 | [Property] get: () => 42 | semmle.label | 1 | +| tst.ts:347:30:350:1 | [ObjectExpr] {get: ...} | tst.ts:348:3:348:15 | [Property] get: () => 42 | semmle.order | 1 | +| tst.ts:347:30:350:1 | [ObjectExpr] {get: ...} | tst.ts:349:3:349:21 | [Property] set: (value) => { } | semmle.label | 2 | +| tst.ts:347:30:350:1 | [ObjectExpr] {get: ...} | tst.ts:349:3:349:21 | [Property] set: (value) => { } | semmle.order | 2 | +| tst.ts:348:3:348:15 | [Property] get: () => 42 | tst.ts:348:3:348:5 | [Label] get | semmle.label | 1 | +| tst.ts:348:3:348:15 | [Property] get: () => 42 | tst.ts:348:3:348:5 | [Label] get | semmle.order | 1 | +| tst.ts:348:3:348:15 | [Property] get: () => 42 | tst.ts:348:8:348:15 | [ArrowFunctionExpr] () => 42 | semmle.label | 2 | +| tst.ts:348:3:348:15 | [Property] get: () => 42 | tst.ts:348:8:348:15 | [ArrowFunctionExpr] () => 42 | semmle.order | 2 | +| tst.ts:348:8:348:15 | [ArrowFunctionExpr] () => 42 | tst.ts:348:14:348:15 | [Literal] 42 | semmle.label | 5 | +| tst.ts:348:8:348:15 | [ArrowFunctionExpr] () => 42 | tst.ts:348:14:348:15 | [Literal] 42 | semmle.order | 5 | +| tst.ts:349:3:349:21 | [Property] set: (value) => { } | tst.ts:349:3:349:5 | [Label] set | semmle.label | 1 | +| tst.ts:349:3:349:21 | [Property] set: (value) => { } | tst.ts:349:3:349:5 | [Label] set | semmle.order | 1 | +| tst.ts:349:3:349:21 | [Property] set: (value) => { } | tst.ts:349:8:349:21 | [ArrowFunctionExpr] (value) => { } | semmle.label | 2 | +| tst.ts:349:3:349:21 | [Property] set: (value) => { } | tst.ts:349:8:349:21 | [ArrowFunctionExpr] (value) => { } | semmle.order | 2 | +| tst.ts:349:8:349:21 | [ArrowFunctionExpr] (value) => { } | file://:0:0:0:0 | (Parameters) | semmle.label | 1 | +| tst.ts:349:8:349:21 | [ArrowFunctionExpr] (value) => { } | file://:0:0:0:0 | (Parameters) | semmle.order | 1 | +| tst.ts:349:8:349:21 | [ArrowFunctionExpr] (value) => { } | tst.ts:349:19:349:21 | [BlockStmt] { } | semmle.label | 5 | +| tst.ts:349:8:349:21 | [ArrowFunctionExpr] (value) => { } | tst.ts:349:19:349:21 | [BlockStmt] { } | semmle.order | 5 | +| tst.ts:352:1:352:29 | [DeclStmt] const fortyTwo = ... | tst.ts:352:7:352:28 | [VariableDeclarator] fortyTw ... e.get() | semmle.label | 1 | +| tst.ts:352:1:352:29 | [DeclStmt] const fortyTwo = ... | tst.ts:352:7:352:28 | [VariableDeclarator] fortyTw ... e.get() | semmle.order | 1 | +| tst.ts:352:7:352:28 | [VariableDeclarator] fortyTw ... e.get() | tst.ts:352:7:352:14 | [VarDecl] fortyTwo | semmle.label | 1 | +| tst.ts:352:7:352:28 | [VariableDeclarator] fortyTw ... e.get() | tst.ts:352:7:352:14 | [VarDecl] fortyTwo | semmle.order | 1 | +| tst.ts:352:7:352:28 | [VariableDeclarator] fortyTw ... e.get() | tst.ts:352:18:352:28 | [MethodCallExpr] state.get() | semmle.label | 2 | +| tst.ts:352:7:352:28 | [VariableDeclarator] fortyTw ... e.get() | tst.ts:352:18:352:28 | [MethodCallExpr] state.get() | semmle.order | 2 | +| tst.ts:352:18:352:26 | [DotExpr] state.get | tst.ts:352:18:352:22 | [VarRef] state | semmle.label | 1 | +| tst.ts:352:18:352:26 | [DotExpr] state.get | tst.ts:352:18:352:22 | [VarRef] state | semmle.order | 1 | +| tst.ts:352:18:352:26 | [DotExpr] state.get | tst.ts:352:24:352:26 | [Label] get | semmle.label | 2 | +| tst.ts:352:18:352:26 | [DotExpr] state.get | tst.ts:352:24:352:26 | [Label] get | semmle.order | 2 | +| tst.ts:352:18:352:28 | [MethodCallExpr] state.get() | tst.ts:352:18:352:26 | [DotExpr] state.get | semmle.label | 0 | +| tst.ts:352:18:352:28 | [MethodCallExpr] state.get() | tst.ts:352:18:352:26 | [DotExpr] state.get | semmle.order | 0 | +| tst.ts:356:1:356:44 | [ImportDeclaration] import ... S.mjs'; | tst.ts:356:8:356:18 | [ImportSpecifier] tstModuleES | semmle.label | 1 | +| tst.ts:356:1:356:44 | [ImportDeclaration] import ... S.mjs'; | tst.ts:356:8:356:18 | [ImportSpecifier] tstModuleES | semmle.order | 1 | +| tst.ts:356:1:356:44 | [ImportDeclaration] import ... S.mjs'; | tst.ts:356:25:356:43 | [Literal] './tstModuleES.mjs' | semmle.label | 2 | +| tst.ts:356:1:356:44 | [ImportDeclaration] import ... S.mjs'; | tst.ts:356:25:356:43 | [Literal] './tstModuleES.mjs' | semmle.order | 2 | +| tst.ts:356:8:356:18 | [ImportSpecifier] tstModuleES | tst.ts:356:8:356:18 | [VarDecl] tstModuleES | semmle.label | 1 | +| tst.ts:356:8:356:18 | [ImportSpecifier] tstModuleES | tst.ts:356:8:356:18 | [VarDecl] tstModuleES | semmle.order | 1 | +| tst.ts:358:1:358:11 | [DotExpr] console.log | tst.ts:358:1:358:7 | [VarRef] console | semmle.label | 1 | +| tst.ts:358:1:358:11 | [DotExpr] console.log | tst.ts:358:1:358:7 | [VarRef] console | semmle.order | 1 | +| tst.ts:358:1:358:11 | [DotExpr] console.log | tst.ts:358:9:358:11 | [Label] log | semmle.label | 2 | +| tst.ts:358:1:358:11 | [DotExpr] console.log | tst.ts:358:9:358:11 | [Label] log | semmle.order | 2 | +| tst.ts:358:1:358:26 | [MethodCallExpr] console ... leES()) | file://:0:0:0:0 | (Arguments) | semmle.label | 1 | +| tst.ts:358:1:358:26 | [MethodCallExpr] console ... leES()) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | +| tst.ts:358:1:358:26 | [MethodCallExpr] console ... leES()) | tst.ts:358:1:358:11 | [DotExpr] console.log | semmle.label | 0 | +| tst.ts:358:1:358:26 | [MethodCallExpr] console ... leES()) | tst.ts:358:1:358:11 | [DotExpr] console.log | semmle.order | 0 | +| tst.ts:358:1:358:27 | [ExprStmt] console ... eES()); | tst.ts:358:1:358:26 | [MethodCallExpr] console ... leES()) | semmle.label | 1 | +| tst.ts:358:1:358:27 | [ExprStmt] console ... eES()); | tst.ts:358:1:358:26 | [MethodCallExpr] console ... leES()) | semmle.order | 1 | +| tst.ts:358:13:358:25 | [CallExpr] tstModuleES() | tst.ts:358:13:358:23 | [VarRef] tstModuleES | semmle.label | 0 | +| tst.ts:358:13:358:25 | [CallExpr] tstModuleES() | tst.ts:358:13:358:23 | [VarRef] tstModuleES | semmle.order | 0 | +| tst.ts:360:1:360:50 | [ImportDeclaration] import ... S.cjs'; | tst.ts:360:10:360:21 | [ImportSpecifier] tstModuleCJS | semmle.label | 1 | +| tst.ts:360:1:360:50 | [ImportDeclaration] import ... S.cjs'; | tst.ts:360:10:360:21 | [ImportSpecifier] tstModuleCJS | semmle.order | 1 | +| tst.ts:360:1:360:50 | [ImportDeclaration] import ... S.cjs'; | tst.ts:360:30:360:49 | [Literal] './tstModuleCJS.cjs' | semmle.label | 2 | +| tst.ts:360:1:360:50 | [ImportDeclaration] import ... S.cjs'; | tst.ts:360:30:360:49 | [Literal] './tstModuleCJS.cjs' | semmle.order | 2 | +| tst.ts:360:10:360:21 | [ImportSpecifier] tstModuleCJS | tst.ts:360:10:360:21 | [Label] tstModuleCJS | semmle.label | 1 | +| tst.ts:360:10:360:21 | [ImportSpecifier] tstModuleCJS | tst.ts:360:10:360:21 | [Label] tstModuleCJS | semmle.order | 1 | +| tst.ts:360:10:360:21 | [ImportSpecifier] tstModuleCJS | tst.ts:360:10:360:21 | [VarDecl] tstModuleCJS | semmle.label | 2 | +| tst.ts:360:10:360:21 | [ImportSpecifier] tstModuleCJS | tst.ts:360:10:360:21 | [VarDecl] tstModuleCJS | semmle.order | 2 | +| tst.ts:362:1:362:11 | [DotExpr] console.log | tst.ts:362:1:362:7 | [VarRef] console | semmle.label | 1 | +| tst.ts:362:1:362:11 | [DotExpr] console.log | tst.ts:362:1:362:7 | [VarRef] console | semmle.order | 1 | +| tst.ts:362:1:362:11 | [DotExpr] console.log | tst.ts:362:9:362:11 | [Label] log | semmle.label | 2 | +| tst.ts:362:1:362:11 | [DotExpr] console.log | tst.ts:362:9:362:11 | [Label] log | semmle.order | 2 | +| tst.ts:362:1:362:27 | [MethodCallExpr] console ... eCJS()) | file://:0:0:0:0 | (Arguments) | semmle.label | 1 | +| tst.ts:362:1:362:27 | [MethodCallExpr] console ... eCJS()) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | +| tst.ts:362:1:362:27 | [MethodCallExpr] console ... eCJS()) | tst.ts:362:1:362:11 | [DotExpr] console.log | semmle.label | 0 | +| tst.ts:362:1:362:27 | [MethodCallExpr] console ... eCJS()) | tst.ts:362:1:362:11 | [DotExpr] console.log | semmle.order | 0 | +| tst.ts:362:1:362:28 | [ExprStmt] console ... CJS()); | tst.ts:362:1:362:27 | [MethodCallExpr] console ... eCJS()) | semmle.label | 1 | +| tst.ts:362:1:362:28 | [ExprStmt] console ... CJS()); | tst.ts:362:1:362:27 | [MethodCallExpr] console ... eCJS()) | semmle.order | 1 | +| tst.ts:362:13:362:26 | [CallExpr] tstModuleCJS() | tst.ts:362:13:362:24 | [VarRef] tstModuleCJS | semmle.label | 0 | +| tst.ts:362:13:362:26 | [CallExpr] tstModuleCJS() | tst.ts:362:13:362:24 | [VarRef] tstModuleCJS | semmle.order | 0 | +| tst.ts:368:1:368:34 | [ImportDeclaration] import ... ffixA'; | tst.ts:368:8:368:13 | [ImportSpecifier] * as A | semmle.label | 1 | +| tst.ts:368:1:368:34 | [ImportDeclaration] import ... ffixA'; | tst.ts:368:8:368:13 | [ImportSpecifier] * as A | semmle.order | 1 | +| tst.ts:368:1:368:34 | [ImportDeclaration] import ... ffixA'; | tst.ts:368:20:368:33 | [Literal] './tstSuffixA' | semmle.label | 2 | +| tst.ts:368:1:368:34 | [ImportDeclaration] import ... ffixA'; | tst.ts:368:20:368:33 | [Literal] './tstSuffixA' | semmle.order | 2 | +| tst.ts:368:8:368:13 | [ImportSpecifier] * as A | tst.ts:368:13:368:13 | [VarDecl] A | semmle.label | 1 | +| tst.ts:368:8:368:13 | [ImportSpecifier] * as A | tst.ts:368:13:368:13 | [VarDecl] A | semmle.order | 1 | +| tst.ts:370:1:370:11 | [DotExpr] console.log | tst.ts:370:1:370:7 | [VarRef] console | semmle.label | 1 | +| tst.ts:370:1:370:11 | [DotExpr] console.log | tst.ts:370:1:370:7 | [VarRef] console | semmle.order | 1 | +| tst.ts:370:1:370:11 | [DotExpr] console.log | tst.ts:370:9:370:11 | [Label] log | semmle.label | 2 | +| tst.ts:370:1:370:11 | [DotExpr] console.log | tst.ts:370:9:370:11 | [Label] log | semmle.order | 2 | +| tst.ts:370:1:370:29 | [MethodCallExpr] console ... File()) | file://:0:0:0:0 | (Arguments) | semmle.label | 1 | +| tst.ts:370:1:370:29 | [MethodCallExpr] console ... File()) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | +| tst.ts:370:1:370:29 | [MethodCallExpr] console ... File()) | tst.ts:370:1:370:11 | [DotExpr] console.log | semmle.label | 0 | +| tst.ts:370:1:370:29 | [MethodCallExpr] console ... File()) | tst.ts:370:1:370:11 | [DotExpr] console.log | semmle.order | 0 | +| tst.ts:370:1:370:30 | [ExprStmt] console ... ile()); | tst.ts:370:1:370:29 | [MethodCallExpr] console ... File()) | semmle.label | 1 | +| tst.ts:370:1:370:30 | [ExprStmt] console ... ile()); | tst.ts:370:1:370:29 | [MethodCallExpr] console ... File()) | semmle.order | 1 | +| tst.ts:370:13:370:26 | [DotExpr] A.resolvedFile | tst.ts:370:13:370:13 | [VarRef] A | semmle.label | 1 | +| tst.ts:370:13:370:26 | [DotExpr] A.resolvedFile | tst.ts:370:13:370:13 | [VarRef] A | semmle.order | 1 | +| tst.ts:370:13:370:26 | [DotExpr] A.resolvedFile | tst.ts:370:15:370:26 | [Label] resolvedFile | semmle.label | 2 | +| tst.ts:370:13:370:26 | [DotExpr] A.resolvedFile | tst.ts:370:15:370:26 | [Label] resolvedFile | semmle.order | 2 | +| tst.ts:370:13:370:28 | [MethodCallExpr] A.resolvedFile() | tst.ts:370:13:370:26 | [DotExpr] A.resolvedFile | semmle.label | 0 | +| tst.ts:370:13:370:28 | [MethodCallExpr] A.resolvedFile() | tst.ts:370:13:370:26 | [DotExpr] A.resolvedFile | semmle.order | 0 | +| tst.ts:372:1:372:34 | [ImportDeclaration] import ... ffixB'; | tst.ts:372:8:372:13 | [ImportSpecifier] * as B | semmle.label | 1 | +| tst.ts:372:1:372:34 | [ImportDeclaration] import ... ffixB'; | tst.ts:372:8:372:13 | [ImportSpecifier] * as B | semmle.order | 1 | +| tst.ts:372:1:372:34 | [ImportDeclaration] import ... ffixB'; | tst.ts:372:20:372:33 | [Literal] './tstSuffixB' | semmle.label | 2 | +| tst.ts:372:1:372:34 | [ImportDeclaration] import ... ffixB'; | tst.ts:372:20:372:33 | [Literal] './tstSuffixB' | semmle.order | 2 | +| tst.ts:372:8:372:13 | [ImportSpecifier] * as B | tst.ts:372:13:372:13 | [VarDecl] B | semmle.label | 1 | +| tst.ts:372:8:372:13 | [ImportSpecifier] * as B | tst.ts:372:13:372:13 | [VarDecl] B | semmle.order | 1 | +| tst.ts:374:1:374:11 | [DotExpr] console.log | tst.ts:374:1:374:7 | [VarRef] console | semmle.label | 1 | +| tst.ts:374:1:374:11 | [DotExpr] console.log | tst.ts:374:1:374:7 | [VarRef] console | semmle.order | 1 | +| tst.ts:374:1:374:11 | [DotExpr] console.log | tst.ts:374:9:374:11 | [Label] log | semmle.label | 2 | +| tst.ts:374:1:374:11 | [DotExpr] console.log | tst.ts:374:9:374:11 | [Label] log | semmle.order | 2 | +| tst.ts:374:1:374:29 | [MethodCallExpr] console ... File()) | file://:0:0:0:0 | (Arguments) | semmle.label | 1 | +| tst.ts:374:1:374:29 | [MethodCallExpr] console ... File()) | file://:0:0:0:0 | (Arguments) | semmle.order | 1 | +| tst.ts:374:1:374:29 | [MethodCallExpr] console ... File()) | tst.ts:374:1:374:11 | [DotExpr] console.log | semmle.label | 0 | +| tst.ts:374:1:374:29 | [MethodCallExpr] console ... File()) | tst.ts:374:1:374:11 | [DotExpr] console.log | semmle.order | 0 | +| tst.ts:374:1:374:30 | [ExprStmt] console ... ile()); | tst.ts:374:1:374:29 | [MethodCallExpr] console ... File()) | semmle.label | 1 | +| tst.ts:374:1:374:30 | [ExprStmt] console ... ile()); | tst.ts:374:1:374:29 | [MethodCallExpr] console ... File()) | semmle.order | 1 | +| tst.ts:374:13:374:26 | [DotExpr] B.resolvedFile | tst.ts:374:13:374:13 | [VarRef] B | semmle.label | 1 | +| tst.ts:374:13:374:26 | [DotExpr] B.resolvedFile | tst.ts:374:13:374:13 | [VarRef] B | semmle.order | 1 | +| tst.ts:374:13:374:26 | [DotExpr] B.resolvedFile | tst.ts:374:15:374:26 | [Label] resolvedFile | semmle.label | 2 | +| tst.ts:374:13:374:26 | [DotExpr] B.resolvedFile | tst.ts:374:15:374:26 | [Label] resolvedFile | semmle.order | 2 | +| tst.ts:374:13:374:28 | [MethodCallExpr] B.resolvedFile() | tst.ts:374:13:374:26 | [DotExpr] B.resolvedFile | semmle.label | 0 | +| tst.ts:374:13:374:28 | [MethodCallExpr] B.resolvedFile() | tst.ts:374:13:374:26 | [DotExpr] B.resolvedFile | semmle.order | 0 | +| tstModuleCJS.cts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | semmle.label | 1 | +| tstModuleCJS.cts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | semmle.order | 1 | +| tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleCJS.cts:1:17:1:28 | [VarDecl] tstModuleCJS | semmle.label | 0 | +| tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleCJS.cts:1:17:1:28 | [VarDecl] tstModuleCJS | semmle.order | 0 | +| tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleCJS.cts:1:33:1:41 | [UnionTypeExpr] 'a' \| 'b' | semmle.label | 4 | +| tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleCJS.cts:1:33:1:41 | [UnionTypeExpr] 'a' \| 'b' | semmle.order | 4 | +| tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleCJS.cts:1:43:3:1 | [BlockStmt] { r ... 'b'; } | semmle.label | 5 | +| tstModuleCJS.cts:1:8:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleCJS.cts:1:43:3:1 | [BlockStmt] { r ... 'b'; } | semmle.order | 5 | +| tstModuleCJS.cts:1:33:1:41 | [UnionTypeExpr] 'a' \| 'b' | tstModuleCJS.cts:1:33:1:35 | [LiteralTypeExpr] 'a' | semmle.label | 1 | +| tstModuleCJS.cts:1:33:1:41 | [UnionTypeExpr] 'a' \| 'b' | tstModuleCJS.cts:1:33:1:35 | [LiteralTypeExpr] 'a' | semmle.order | 1 | +| tstModuleCJS.cts:1:33:1:41 | [UnionTypeExpr] 'a' \| 'b' | tstModuleCJS.cts:1:39:1:41 | [LiteralTypeExpr] 'b' | semmle.label | 2 | +| tstModuleCJS.cts:1:33:1:41 | [UnionTypeExpr] 'a' \| 'b' | tstModuleCJS.cts:1:39:1:41 | [LiteralTypeExpr] 'b' | semmle.order | 2 | +| tstModuleCJS.cts:1:43:3:1 | [BlockStmt] { r ... 'b'; } | tstModuleCJS.cts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | semmle.label | 1 | +| tstModuleCJS.cts:1:43:3:1 | [BlockStmt] { r ... 'b'; } | tstModuleCJS.cts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | semmle.order | 1 | +| tstModuleCJS.cts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | semmle.label | 1 | +| tstModuleCJS.cts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | semmle.order | 1 | +| tstModuleCJS.cts:2:12:2:22 | [DotExpr] Math.random | tstModuleCJS.cts:2:12:2:15 | [VarRef] Math | semmle.label | 1 | +| tstModuleCJS.cts:2:12:2:22 | [DotExpr] Math.random | tstModuleCJS.cts:2:12:2:15 | [VarRef] Math | semmle.order | 1 | +| tstModuleCJS.cts:2:12:2:22 | [DotExpr] Math.random | tstModuleCJS.cts:2:17:2:22 | [Label] random | semmle.label | 2 | +| tstModuleCJS.cts:2:12:2:22 | [DotExpr] Math.random | tstModuleCJS.cts:2:17:2:22 | [Label] random | semmle.order | 2 | +| tstModuleCJS.cts:2:12:2:24 | [MethodCallExpr] Math.random() | tstModuleCJS.cts:2:12:2:22 | [DotExpr] Math.random | semmle.label | 0 | +| tstModuleCJS.cts:2:12:2:24 | [MethodCallExpr] Math.random() | tstModuleCJS.cts:2:12:2:22 | [DotExpr] Math.random | semmle.order | 0 | +| tstModuleCJS.cts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleCJS.cts:2:12:2:24 | [MethodCallExpr] Math.random() | semmle.label | 1 | +| tstModuleCJS.cts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleCJS.cts:2:12:2:24 | [MethodCallExpr] Math.random() | semmle.order | 1 | +| tstModuleCJS.cts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleCJS.cts:2:28:2:30 | [Literal] 0.5 | semmle.label | 2 | +| tstModuleCJS.cts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleCJS.cts:2:28:2:30 | [Literal] 0.5 | semmle.order | 2 | +| tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleCJS.cts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | semmle.label | 1 | +| tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleCJS.cts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | semmle.order | 1 | +| tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleCJS.cts:2:34:2:36 | [Literal] 'a' | semmle.label | 2 | +| tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleCJS.cts:2:34:2:36 | [Literal] 'a' | semmle.order | 2 | +| tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleCJS.cts:2:40:2:42 | [Literal] 'b' | semmle.label | 3 | +| tstModuleCJS.cts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleCJS.cts:2:40:2:42 | [Literal] 'b' | semmle.order | 3 | +| tstModuleES.mts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | semmle.label | 1 | +| tstModuleES.mts:1:1:3:1 | [ExportDeclaration] export ... 'b'; } | tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | semmle.order | 1 | +| tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleES.mts:1:25:1:35 | [VarDecl] tstModuleES | semmle.label | 0 | +| tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleES.mts:1:25:1:35 | [VarDecl] tstModuleES | semmle.order | 0 | +| tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleES.mts:1:40:1:48 | [UnionTypeExpr] 'a' \| 'b' | semmle.label | 4 | +| tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleES.mts:1:40:1:48 | [UnionTypeExpr] 'a' \| 'b' | semmle.order | 4 | +| tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleES.mts:1:50:3:1 | [BlockStmt] { r ... 'b'; } | semmle.label | 5 | +| tstModuleES.mts:1:16:3:1 | [FunctionDeclStmt] functio ... 'b'; } | tstModuleES.mts:1:50:3:1 | [BlockStmt] { r ... 'b'; } | semmle.order | 5 | +| tstModuleES.mts:1:40:1:48 | [UnionTypeExpr] 'a' \| 'b' | tstModuleES.mts:1:40:1:42 | [LiteralTypeExpr] 'a' | semmle.label | 1 | +| tstModuleES.mts:1:40:1:48 | [UnionTypeExpr] 'a' \| 'b' | tstModuleES.mts:1:40:1:42 | [LiteralTypeExpr] 'a' | semmle.order | 1 | +| tstModuleES.mts:1:40:1:48 | [UnionTypeExpr] 'a' \| 'b' | tstModuleES.mts:1:46:1:48 | [LiteralTypeExpr] 'b' | semmle.label | 2 | +| tstModuleES.mts:1:40:1:48 | [UnionTypeExpr] 'a' \| 'b' | tstModuleES.mts:1:46:1:48 | [LiteralTypeExpr] 'b' | semmle.order | 2 | +| tstModuleES.mts:1:50:3:1 | [BlockStmt] { r ... 'b'; } | tstModuleES.mts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | semmle.label | 1 | +| tstModuleES.mts:1:50:3:1 | [BlockStmt] { r ... 'b'; } | tstModuleES.mts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | semmle.order | 1 | +| tstModuleES.mts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | semmle.label | 1 | +| tstModuleES.mts:2:5:2:43 | [ReturnStmt] return ... : 'b'; | tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | semmle.order | 1 | +| tstModuleES.mts:2:12:2:22 | [DotExpr] Math.random | tstModuleES.mts:2:12:2:15 | [VarRef] Math | semmle.label | 1 | +| tstModuleES.mts:2:12:2:22 | [DotExpr] Math.random | tstModuleES.mts:2:12:2:15 | [VarRef] Math | semmle.order | 1 | +| tstModuleES.mts:2:12:2:22 | [DotExpr] Math.random | tstModuleES.mts:2:17:2:22 | [Label] random | semmle.label | 2 | +| tstModuleES.mts:2:12:2:22 | [DotExpr] Math.random | tstModuleES.mts:2:17:2:22 | [Label] random | semmle.order | 2 | +| tstModuleES.mts:2:12:2:24 | [MethodCallExpr] Math.random() | tstModuleES.mts:2:12:2:22 | [DotExpr] Math.random | semmle.label | 0 | +| tstModuleES.mts:2:12:2:24 | [MethodCallExpr] Math.random() | tstModuleES.mts:2:12:2:22 | [DotExpr] Math.random | semmle.order | 0 | +| tstModuleES.mts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleES.mts:2:12:2:24 | [MethodCallExpr] Math.random() | semmle.label | 1 | +| tstModuleES.mts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleES.mts:2:12:2:24 | [MethodCallExpr] Math.random() | semmle.order | 1 | +| tstModuleES.mts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleES.mts:2:28:2:30 | [Literal] 0.5 | semmle.label | 2 | +| tstModuleES.mts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | tstModuleES.mts:2:28:2:30 | [Literal] 0.5 | semmle.order | 2 | +| tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleES.mts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | semmle.label | 1 | +| tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleES.mts:2:12:2:30 | [BinaryExpr] Math.random() > 0.5 | semmle.order | 1 | +| tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleES.mts:2:34:2:36 | [Literal] 'a' | semmle.label | 2 | +| tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleES.mts:2:34:2:36 | [Literal] 'a' | semmle.order | 2 | +| tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleES.mts:2:40:2:42 | [Literal] 'b' | semmle.label | 3 | +| tstModuleES.mts:2:12:2:42 | [ConditionalExpr] Math.ra ... ' : 'b' | tstModuleES.mts:2:40:2:42 | [Literal] 'b' | semmle.order | 3 | +| tstSuffixA.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.label | 1 | +| tstSuffixA.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.order | 1 | +| tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixA.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.label | 0 | +| tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixA.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.order | 0 | +| tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixA.ts:1:33:1:47 | [LiteralTypeExpr] 'tstSuffixA.ts' | semmle.label | 4 | +| tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixA.ts:1:33:1:47 | [LiteralTypeExpr] 'tstSuffixA.ts' | semmle.order | 4 | +| tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixA.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | semmle.label | 5 | +| tstSuffixA.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixA.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | semmle.order | 5 | +| tstSuffixA.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | tstSuffixA.ts:2:5:2:27 | [ReturnStmt] return ... xA.ts'; | semmle.label | 1 | +| tstSuffixA.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | tstSuffixA.ts:2:5:2:27 | [ReturnStmt] return ... xA.ts'; | semmle.order | 1 | +| tstSuffixA.ts:2:5:2:27 | [ReturnStmt] return ... xA.ts'; | tstSuffixA.ts:2:12:2:26 | [Literal] 'tstSuffixA.ts' | semmle.label | 1 | +| tstSuffixA.ts:2:5:2:27 | [ReturnStmt] return ... xA.ts'; | tstSuffixA.ts:2:12:2:26 | [Literal] 'tstSuffixA.ts' | semmle.order | 1 | +| tstSuffixB.ios.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.label | 1 | +| tstSuffixB.ios.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.order | 1 | +| tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ios.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.label | 0 | +| tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ios.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.order | 0 | +| tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ios.ts:1:33:1:51 | [LiteralTypeExpr] 'tstSuffixB.ios.ts' | semmle.label | 4 | +| tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ios.ts:1:33:1:51 | [LiteralTypeExpr] 'tstSuffixB.ios.ts' | semmle.order | 4 | +| tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ios.ts:1:53:3:1 | [BlockStmt] { r ... .ts'; } | semmle.label | 5 | +| tstSuffixB.ios.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ios.ts:1:53:3:1 | [BlockStmt] { r ... .ts'; } | semmle.order | 5 | +| tstSuffixB.ios.ts:1:53:3:1 | [BlockStmt] { r ... .ts'; } | tstSuffixB.ios.ts:2:5:2:31 | [ReturnStmt] return ... os.ts'; | semmle.label | 1 | +| tstSuffixB.ios.ts:1:53:3:1 | [BlockStmt] { r ... .ts'; } | tstSuffixB.ios.ts:2:5:2:31 | [ReturnStmt] return ... os.ts'; | semmle.order | 1 | +| tstSuffixB.ios.ts:2:5:2:31 | [ReturnStmt] return ... os.ts'; | tstSuffixB.ios.ts:2:12:2:30 | [Literal] 'tstSuffixB.ios.ts' | semmle.label | 1 | +| tstSuffixB.ios.ts:2:5:2:31 | [ReturnStmt] return ... os.ts'; | tstSuffixB.ios.ts:2:12:2:30 | [Literal] 'tstSuffixB.ios.ts' | semmle.order | 1 | +| tstSuffixB.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.label | 1 | +| tstSuffixB.ts:1:1:3:1 | [ExportDeclaration] export ... .ts'; } | tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | semmle.order | 1 | +| tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.label | 0 | +| tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ts:1:17:1:28 | [VarDecl] resolvedFile | semmle.order | 0 | +| tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ts:1:33:1:47 | [LiteralTypeExpr] 'tstSuffixB.ts' | semmle.label | 4 | +| tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ts:1:33:1:47 | [LiteralTypeExpr] 'tstSuffixB.ts' | semmle.order | 4 | +| tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | semmle.label | 5 | +| tstSuffixB.ts:1:8:3:1 | [FunctionDeclStmt] functio ... .ts'; } | tstSuffixB.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | semmle.order | 5 | +| tstSuffixB.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | tstSuffixB.ts:2:5:2:27 | [ReturnStmt] return ... xB.ts'; | semmle.label | 1 | +| tstSuffixB.ts:1:49:3:1 | [BlockStmt] { r ... .ts'; } | tstSuffixB.ts:2:5:2:27 | [ReturnStmt] return ... xB.ts'; | semmle.order | 1 | +| tstSuffixB.ts:2:5:2:27 | [ReturnStmt] return ... xB.ts'; | tstSuffixB.ts:2:12:2:26 | [Literal] 'tstSuffixB.ts' | semmle.label | 1 | +| tstSuffixB.ts:2:5:2:27 | [ReturnStmt] return ... xB.ts'; | tstSuffixB.ts:2:12:2:26 | [Literal] 'tstSuffixB.ts' | semmle.order | 1 | | type_alias.ts:1:1:1:17 | [TypeAliasDeclaration,TypeDefinition] type B = boolean; | type_alias.ts:1:6:1:6 | [Identifier] B | semmle.label | 1 | | type_alias.ts:1:1:1:17 | [TypeAliasDeclaration,TypeDefinition] type B = boolean; | type_alias.ts:1:6:1:6 | [Identifier] B | semmle.order | 1 | | type_alias.ts:1:1:1:17 | [TypeAliasDeclaration,TypeDefinition] type B = boolean; | type_alias.ts:1:10:1:16 | [KeywordTypeExpr] boolean | semmle.label | 2 | diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tests.expected b/javascript/ql/test/library-tests/TypeScript/Types/tests.expected index 4e85a6fe95c..ce517db8d6d 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/tests.expected +++ b/javascript/ql/test/library-tests/TypeScript/Types/tests.expected @@ -386,6 +386,148 @@ getExprType | tst.ts:293:7:293:21 | payload.toFixed | (fractionDigits?: number) => string | | tst.ts:293:7:293:23 | payload.toFixed() | string | | tst.ts:293:15:293:21 | toFixed | (fractionDigits?: number) => string | +| tst.ts:298:7:298:9 | key | typeof key | +| tst.ts:298:13:298:18 | Symbol | SymbolConstructor | +| tst.ts:298:13:298:20 | Symbol() | typeof key | +| tst.ts:300:7:300:20 | numberOrString | "hello" \| 42 | +| tst.ts:300:24:300:27 | Math | Math | +| tst.ts:300:24:300:34 | Math.random | () => number | +| tst.ts:300:24:300:36 | Math.random() | number | +| tst.ts:300:24:300:42 | Math.random() < 0.5 | boolean | +| tst.ts:300:24:300:57 | Math.ra ... "hello" | "hello" \| 42 | +| tst.ts:300:29:300:34 | random | () => number | +| tst.ts:300:40:300:42 | 0.5 | 0.5 | +| tst.ts:300:46:300:47 | 42 | 42 | +| tst.ts:300:51:300:57 | "hello" | "hello" | +| tst.ts:302:5:302:7 | obj | { [key]: string \| number; } | +| tst.ts:302:11:304:1 | {\\n [ke ... ring,\\n} | { [key]: string \| number; } | +| tst.ts:303:4:303:6 | key | typeof key | +| tst.ts:303:10:303:23 | numberOrString | "hello" \| 42 | +| tst.ts:306:5:306:19 | typeof obj[key] | "string" \| "number" \| "bigint" \| "boolean" \| "s... | +| tst.ts:306:5:306:32 | typeof ... string" | boolean | +| tst.ts:306:12:306:14 | obj | { [key]: string \| number; } | +| tst.ts:306:12:306:19 | obj[key] | string \| number | +| tst.ts:306:16:306:18 | key | typeof key | +| tst.ts:306:25:306:32 | "string" | "string" | +| tst.ts:307:7:307:9 | str | string | +| tst.ts:307:13:307:15 | obj | { [key]: string \| number; } | +| tst.ts:307:13:307:20 | obj[key] | string | +| tst.ts:307:17:307:19 | key | typeof key | +| tst.ts:308:3:308:5 | str | string | +| tst.ts:308:3:308:17 | str.toUpperCase | () => string | +| tst.ts:308:3:308:19 | str.toUpperCase() | string | +| tst.ts:308:7:308:17 | toUpperCase | () => string | +| tst.ts:313:10:313:10 | f | (arg: { produce: (n: string) => T; consume: ... | +| tst.ts:313:15:313:17 | arg | { produce: (n: string) => T; consume: (x: T) =>... | +| tst.ts:314:3:314:9 | produce | (n: string) => T | +| tst.ts:314:12:314:27 | (n: string) => T | (n: string) => T | +| tst.ts:314:13:314:13 | n | string | +| tst.ts:315:3:315:9 | consume | (x: T) => void | +| tst.ts:315:12:315:25 | (x: T) => void | (x: T) => void | +| tst.ts:315:13:315:13 | x | T | +| tst.ts:318:1:318:1 | f | (arg: { produce: (n: string) => T; consume: ... | +| tst.ts:318:1:321:2 | f({\\n p ... se()\\n}) | void | +| tst.ts:318:3:321:1 | {\\n pro ... ase()\\n} | { produce: (n: string) => string; consume: (x: ... | +| tst.ts:319:3:319:9 | produce | (n: string) => string | +| tst.ts:319:12:319:12 | n | string | +| tst.ts:319:12:319:17 | n => n | (n: string) => string | +| tst.ts:319:17:319:17 | n | string | +| tst.ts:320:3:320:9 | consume | (x: string) => string | +| tst.ts:320:12:320:12 | x | string | +| tst.ts:320:12:320:31 | x => x.toLowerCase() | (x: string) => string | +| tst.ts:320:17:320:17 | x | string | +| tst.ts:320:17:320:29 | x.toLowerCase | () => string | +| tst.ts:320:17:320:31 | x.toLowerCase() | string | +| tst.ts:320:19:320:29 | toLowerCase | () => string | +| tst.ts:325:7:325:14 | ErrorMap | { new (entries?: readonly (readonly [string, Er... | +| tst.ts:325:18:325:20 | Map | MapConstructor | +| tst.ts:325:18:325:35 | Map | any | +| tst.ts:327:7:327:14 | errorMap | Map | +| tst.ts:327:18:327:31 | new ErrorMap() | Map | +| tst.ts:327:22:327:29 | ErrorMap | { new (entries?: readonly (readonly [string, Er... | +| tst.ts:338:7:338:7 | a | "a" \| "b" | +| tst.ts:338:14:338:16 | 'a' | "a" | +| tst.ts:343:3:343:5 | get | () => T | +| tst.ts:343:8:343:14 | () => T | () => T | +| tst.ts:344:3:344:5 | set | (value: T) => void | +| tst.ts:344:8:344:25 | (value: T) => void | (value: T) => void | +| tst.ts:344:9:344:13 | value | T | +| tst.ts:347:7:347:11 | state | State | +| tst.ts:347:30:350:1 | {\\n get ... > { }\\n} | State | +| tst.ts:348:3:348:5 | get | () => number | +| tst.ts:348:8:348:15 | () => 42 | () => number | +| tst.ts:348:14:348:15 | 42 | 42 | +| tst.ts:349:3:349:5 | set | (value: number) => void | +| tst.ts:349:8:349:21 | (value) => { } | (value: number) => void | +| tst.ts:349:9:349:13 | value | number | +| tst.ts:352:7:352:14 | fortyTwo | number | +| tst.ts:352:18:352:22 | state | State | +| tst.ts:352:18:352:26 | state.get | () => number | +| tst.ts:352:18:352:28 | state.get() | number | +| tst.ts:352:24:352:26 | get | () => number | +| tst.ts:356:8:356:18 | tstModuleES | () => "a" \| "b" | +| tst.ts:356:25:356:43 | './tstModuleES.mjs' | any | +| tst.ts:358:1:358:7 | console | Console | +| tst.ts:358:1:358:11 | console.log | (...data: any[]) => void | +| tst.ts:358:1:358:26 | console ... leES()) | void | +| tst.ts:358:9:358:11 | log | (...data: any[]) => void | +| tst.ts:358:13:358:23 | tstModuleES | () => "a" \| "b" | +| tst.ts:358:13:358:25 | tstModuleES() | "a" \| "b" | +| tst.ts:360:10:360:21 | tstModuleCJS | () => "a" \| "b" | +| tst.ts:360:10:360:21 | tstModuleCJS | () => "a" \| "b" | +| tst.ts:360:30:360:49 | './tstModuleCJS.cjs' | any | +| tst.ts:362:1:362:7 | console | Console | +| tst.ts:362:1:362:11 | console.log | (...data: any[]) => void | +| tst.ts:362:1:362:27 | console ... eCJS()) | void | +| tst.ts:362:9:362:11 | log | (...data: any[]) => void | +| tst.ts:362:13:362:24 | tstModuleCJS | () => "a" \| "b" | +| tst.ts:362:13:362:26 | tstModuleCJS() | "a" \| "b" | +| tst.ts:368:13:368:13 | A | typeof library-tests/TypeScript/Types/tstSuffixA.ts | +| tst.ts:368:20:368:33 | './tstSuffixA' | any | +| tst.ts:370:1:370:7 | console | Console | +| tst.ts:370:1:370:11 | console.log | (...data: any[]) => void | +| tst.ts:370:1:370:29 | console ... File()) | void | +| tst.ts:370:9:370:11 | log | (...data: any[]) => void | +| tst.ts:370:13:370:13 | A | typeof library-tests/TypeScript/Types/tstSuffixA.ts | +| tst.ts:370:13:370:26 | A.resolvedFile | () => "tstSuffixA.ts" | +| tst.ts:370:13:370:28 | A.resolvedFile() | "tstSuffixA.ts" | +| tst.ts:370:15:370:26 | resolvedFile | () => "tstSuffixA.ts" | +| tst.ts:372:13:372:13 | B | typeof library-tests/TypeScript/Types/tstSuffixB.ios.ts | +| tst.ts:372:20:372:33 | './tstSuffixB' | any | +| tst.ts:374:1:374:7 | console | Console | +| tst.ts:374:1:374:11 | console.log | (...data: any[]) => void | +| tst.ts:374:1:374:29 | console ... File()) | void | +| tst.ts:374:9:374:11 | log | (...data: any[]) => void | +| tst.ts:374:13:374:13 | B | typeof library-tests/TypeScript/Types/tstSuffixB.ios.ts | +| tst.ts:374:13:374:26 | B.resolvedFile | () => "tstSuffixB.ios.ts" | +| tst.ts:374:13:374:28 | B.resolvedFile() | "tstSuffixB.ios.ts" | +| tst.ts:374:15:374:26 | resolvedFile | () => "tstSuffixB.ios.ts" | +| tstModuleCJS.cts:1:17:1:28 | tstModuleCJS | () => "a" \| "b" | +| tstModuleCJS.cts:2:12:2:15 | Math | Math | +| tstModuleCJS.cts:2:12:2:22 | Math.random | () => number | +| tstModuleCJS.cts:2:12:2:24 | Math.random() | number | +| tstModuleCJS.cts:2:12:2:30 | Math.random() > 0.5 | boolean | +| tstModuleCJS.cts:2:12:2:42 | Math.ra ... ' : 'b' | "a" \| "b" | +| tstModuleCJS.cts:2:17:2:22 | random | () => number | +| tstModuleCJS.cts:2:28:2:30 | 0.5 | 0.5 | +| tstModuleCJS.cts:2:34:2:36 | 'a' | "a" | +| tstModuleCJS.cts:2:40:2:42 | 'b' | "b" | +| tstModuleES.mts:1:25:1:35 | tstModuleES | () => "a" \| "b" | +| tstModuleES.mts:2:12:2:15 | Math | Math | +| tstModuleES.mts:2:12:2:22 | Math.random | () => number | +| tstModuleES.mts:2:12:2:24 | Math.random() | number | +| tstModuleES.mts:2:12:2:30 | Math.random() > 0.5 | boolean | +| tstModuleES.mts:2:12:2:42 | Math.ra ... ' : 'b' | "a" \| "b" | +| tstModuleES.mts:2:17:2:22 | random | () => number | +| tstModuleES.mts:2:28:2:30 | 0.5 | 0.5 | +| tstModuleES.mts:2:34:2:36 | 'a' | "a" | +| tstModuleES.mts:2:40:2:42 | 'b' | "b" | +| tstSuffixA.ts:1:17:1:28 | resolvedFile | () => "tstSuffixA.ts" | +| tstSuffixA.ts:2:12:2:26 | 'tstSuffixA.ts' | "tstSuffixA.ts" | +| tstSuffixB.ios.ts:1:17:1:28 | resolvedFile | () => "tstSuffixB.ios.ts" | +| tstSuffixB.ios.ts:2:12:2:30 | 'tstSuffixB.ios.ts' | "tstSuffixB.ios.ts" | +| tstSuffixB.ts:1:17:1:28 | resolvedFile | () => "tstSuffixB.ts" | +| tstSuffixB.ts:2:12:2:26 | 'tstSuffixB.ts' | "tstSuffixB.ts" | | type_alias.ts:3:5:3:5 | b | boolean | | type_alias.ts:7:5:7:5 | c | ValueOrArray | | type_alias.ts:14:9:14:32 | [proper ... ]: Json | any | @@ -461,6 +603,9 @@ getTypeDefinitionType | tst.ts:265:3:269:3 | interfa ... an;\\n } | TypeMap | | tst.ts:271:3:276:7 | type Un ... }[P]; | UnionRecord

| | tst.ts:289:3:289:63 | type Fu ... > void; | Func | +| tst.ts:331:1:334:14 | type Fi ... never; | FirstString | +| tst.ts:336:1:336:51 | type F ... lean]>; | "a" \| "b" | +| tst.ts:342:1:345:1 | interfa ... void;\\n} | State | | type_alias.ts:1:1:1:17 | type B = boolean; | boolean | | type_alias.ts:5:1:5:50 | type Va ... ay>; | ValueOrArray | | type_alias.ts:9:1:15:13 | type Js ... Json[]; | Json | @@ -703,6 +848,48 @@ getTypeExprType | tst.ts:289:47:289:52 | string | string | | tst.ts:289:59:289:62 | void | void | | tst.ts:291:13:291:16 | Func | Func | +| tst.ts:313:12:313:12 | T | T | +| tst.ts:313:20:315:27 | {\\n pro ... void } | { produce: (n: string) => T; consume: (x: T) =>... | +| tst.ts:314:12:314:27 | (n: string) => T | (n: string) => T | +| tst.ts:314:16:314:21 | string | string | +| tst.ts:314:27:314:27 | T | T | +| tst.ts:315:12:315:25 | (x: T) => void | (x: T) => void | +| tst.ts:315:16:315:16 | T | T | +| tst.ts:315:22:315:25 | void | void | +| tst.ts:316:4:316:7 | void | void | +| tst.ts:325:22:325:27 | string | string | +| tst.ts:325:30:325:34 | Error | Error | +| tst.ts:331:6:331:16 | FirstString | FirstString | +| tst.ts:331:18:331:18 | T | T | +| tst.ts:336:6:336:6 | F | "a" \| "b" | +| tst.ts:336:10:336:20 | FirstString | FirstString | +| tst.ts:336:10:336:50 | FirstSt ... olean]> | "a" \| "b" | +| tst.ts:336:22:336:49 | ['a' \| ... oolean] | ["a" \| "b", number, boolean] | +| tst.ts:336:23:336:25 | 'a' | "a" | +| tst.ts:336:23:336:31 | 'a' \| 'b' | "a" \| "b" | +| tst.ts:336:29:336:31 | 'b' | "b" | +| tst.ts:336:34:336:39 | number | number | +| tst.ts:336:42:336:48 | boolean | boolean | +| tst.ts:338:10:338:10 | F | "a" \| "b" | +| tst.ts:342:11:342:15 | State | State | +| tst.ts:342:24:342:24 | T | T | +| tst.ts:343:8:343:14 | () => T | () => T | +| tst.ts:343:14:343:14 | T | T | +| tst.ts:344:8:344:25 | (value: T) => void | (value: T) => void | +| tst.ts:344:16:344:16 | T | T | +| tst.ts:344:22:344:25 | void | void | +| tst.ts:347:14:347:18 | State | State | +| tst.ts:347:14:347:26 | State | State | +| tst.ts:347:20:347:25 | number | number | +| tstModuleCJS.cts:1:33:1:35 | 'a' | "a" | +| tstModuleCJS.cts:1:33:1:41 | 'a' \| 'b' | "a" \| "b" | +| tstModuleCJS.cts:1:39:1:41 | 'b' | "b" | +| tstModuleES.mts:1:40:1:42 | 'a' | "a" | +| tstModuleES.mts:1:40:1:48 | 'a' \| 'b' | "a" \| "b" | +| tstModuleES.mts:1:46:1:48 | 'b' | "b" | +| tstSuffixA.ts:1:33:1:47 | 'tstSuffixA.ts' | "tstSuffixA.ts" | +| tstSuffixB.ios.ts:1:33:1:51 | 'tstSuffixB.ios.ts' | "tstSuffixB.ios.ts" | +| tstSuffixB.ts:1:33:1:47 | 'tstSuffixB.ts' | "tstSuffixB.ts" | | type_alias.ts:1:6:1:6 | B | boolean | | type_alias.ts:1:10:1:16 | boolean | boolean | | type_alias.ts:3:8:3:8 | B | boolean | @@ -783,6 +970,7 @@ referenceDefinition | E | type_definition_objects.ts:6:8:6:16 | enum E {} | | EnumWithOneMember | type_definitions.ts:18:26:18:31 | member | | Error | tst.ts:210:10:213:3 | interfa ... ng;\\n } | +| FirstString | tst.ts:331:1:334:14 | type Fi ... never; | | Foo | tst.ts:116:3:129:3 | class F ... }\\n } | | Foo | tst.ts:165:5:167:5 | interfa ... ;\\n } | | Foo | tst.ts:179:3:192:3 | class F ... \\n } | @@ -796,6 +984,8 @@ referenceDefinition | NonAbstractDummy | tst.ts:54:1:56:1 | interfa ... mber;\\n} | | Person | tst.ts:222:3:234:3 | class P ... }\\n } | | Shape | tst.ts:140:3:142:47 | type Sh ... mber }; | +| State | tst.ts:342:1:345:1 | interfa ... void;\\n} | +| State | tst.ts:342:1:345:1 | interfa ... void;\\n} | | Sub | tst.ts:97:3:101:3 | class S ... }\\n } | | Success | tst.ts:205:10:208:3 | interfa ... ng;\\n } | | Super | tst.ts:91:3:95:3 | class S ... }\\n } | @@ -852,16 +1042,20 @@ abstractSignature unionIndex | 1 | 0 | 1 \| 2 | | 2 | 1 | 1 \| 2 | +| 42 | 1 | "hello" \| 42 | | "NumberContents" | 0 | "NumberContents" \| "StringContents" | | "StringContents" | 1 | "NumberContents" \| "StringContents" | | "a" | 0 | "a" \| "b" | | "a" | 1 | number \| "a" | +| "a" | 3 | number \| boolean \| "a" \| "b" | | "b" | 1 | "a" \| "b" | +| "b" | 4 | number \| boolean \| "a" \| "b" | | "bigint" | 2 | "string" \| "number" \| "bigint" \| "boolean" \| "s... | | "boolean" | 2 | keyof TypeMap | | "boolean" | 3 | "string" \| "number" \| "bigint" \| "boolean" \| "s... | | "circle" | 0 | "circle" \| "square" | | "function" | 7 | "string" \| "number" \| "bigint" \| "boolean" \| "s... | +| "hello" | 0 | "hello" \| 42 | | "number" | 1 | "string" \| "number" \| "bigint" \| "boolean" \| "s... | | "number" | 1 | keyof TypeMap | | "object" | 6 | "string" \| "number" \| "bigint" \| "boolean" \| "s... | @@ -871,6 +1065,7 @@ unionIndex | "symbol" | 4 | "string" \| "number" \| "bigint" \| "boolean" \| "s... | | "undefined" | 5 | "string" \| "number" \| "bigint" \| "boolean" \| "s... | | Error | 1 | Success \| Error | +| Error | 1 | string \| Error | | Json[] | 5 | string \| number \| boolean \| { [property: string... | | Promise | 2 | boolean \| Promise | | PromiseLike | 1 | TResult1 \| PromiseLike | @@ -890,16 +1085,19 @@ unionIndex | false | 0 | boolean | | false | 0 | boolean \| Promise | | false | 1 | number \| boolean | +| false | 1 | number \| boolean \| "a" \| "b" | | false | 2 | string \| number \| boolean | | false | 2 | string \| number \| boolean \| { [property: string... | | number | 0 | number \| "a" | | number | 0 | number \| ValueOrArray[] | | number | 0 | number \| boolean | +| number | 0 | number \| boolean \| "a" \| "b" | | number | 1 | string \| number | | number | 1 | string \| number \| boolean | | number | 1 | string \| number \| boolean \| { [property: string... | | number | 1 | string \| number \| true | | string | 0 | VirtualNode \| { [key: string]: any; } | +| string | 0 | string \| Error | | string | 0 | string \| [string, { [key: string]: any; }, ...V... | | string | 0 | string \| number | | string | 0 | string \| number \| boolean | @@ -911,6 +1109,7 @@ unionIndex | true | 1 | boolean | | true | 1 | boolean \| Promise | | true | 2 | number \| boolean | +| true | 2 | number \| boolean \| "a" \| "b" | | true | 2 | string \| number \| true | | true | 3 | string \| number \| boolean | | true | 3 | string \| number \| boolean \| { [property: string... | diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tsconfig.json b/javascript/ql/test/library-tests/TypeScript/Types/tsconfig.json index f6aa3a1e752..2235cec7f7d 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/tsconfig.json +++ b/javascript/ql/test/library-tests/TypeScript/Types/tsconfig.json @@ -3,6 +3,7 @@ "module": "esnext", "target": "esnext", "lib": ["dom", "esnext"], - "resolveJsonModule": true + "resolveJsonModule": true, + "moduleSuffixes": [".ios", ""] } } diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tst.ts b/javascript/ql/test/library-tests/TypeScript/Types/tst.ts index 9fdb0d950c4..ed8787112d3 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/tst.ts +++ b/javascript/ql/test/library-tests/TypeScript/Types/tst.ts @@ -293,4 +293,83 @@ module TS46 { payload.toFixed(); // <- number } }; -} \ No newline at end of file +} + +const key = Symbol(); + +const numberOrString = Math.random() < 0.5 ? 42 : "hello"; + +let obj = { + [key]: numberOrString, +}; + +if (typeof obj[key] === "string") { + let str = obj[key]; // <- string + str.toUpperCase(); +} + +////////// + +function f(arg: { + produce: (n: string) => T, + consume: (x: T) => void } +): void {}; + +f({ + produce: n => n, // <- (n: string) => string + consume: x => x.toLowerCase() +}); + +/////////// + +const ErrorMap = Map; + +const errorMap = new ErrorMap(); // <- Map + +//////////// + +type FirstString = + T extends [infer S extends string, ...unknown[]] + ? S + : never; + +type F = FirstString<['a' | 'b', number, boolean]>; + +const a: F = 'a'; // <- 'a' | 'b' + +//////////// + +interface State { + get: () => T; + set: (value: T) => void; +} + +const state: State = { + get: () => 42, + set: (value) => { } +} + +const fortyTwo = state.get(); // <- number + +///////////////// + +import tstModuleES from './tstModuleES.mjs'; + +console.log(tstModuleES()); + +import { tstModuleCJS } from './tstModuleCJS.cjs'; + +console.log(tstModuleCJS()); + +///////////////// + +// test file resolution order (see tsconfig: moduleSuffixes setting) + +import * as A from './tstSuffixA'; + +console.log(A.resolvedFile()); // <- 'tstSuffixA.ts' + +import * as B from './tstSuffixB'; + +console.log(B.resolvedFile()); // <- 'tstSuffixB.ios.ts' + diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tstModuleCJS.cts b/javascript/ql/test/library-tests/TypeScript/Types/tstModuleCJS.cts new file mode 100644 index 00000000000..c764c1e1243 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/Types/tstModuleCJS.cts @@ -0,0 +1,3 @@ +export function tstModuleCJS(): 'a' | 'b' { + return Math.random() > 0.5 ? 'a' : 'b'; +} diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tstModuleES.mts b/javascript/ql/test/library-tests/TypeScript/Types/tstModuleES.mts new file mode 100644 index 00000000000..cf735d1bb49 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/Types/tstModuleES.mts @@ -0,0 +1,3 @@ +export default function tstModuleES(): 'a' | 'b' { + return Math.random() > 0.5 ? 'a' : 'b'; +} diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixA.ts b/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixA.ts new file mode 100644 index 00000000000..ffe0b811492 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixA.ts @@ -0,0 +1,3 @@ +export function resolvedFile(): 'tstSuffixA.ts' { + return 'tstSuffixA.ts'; +} diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixB.ios.ts b/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixB.ios.ts new file mode 100644 index 00000000000..04463fc7699 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixB.ios.ts @@ -0,0 +1,3 @@ +export function resolvedFile(): 'tstSuffixB.ios.ts' { + return 'tstSuffixB.ios.ts'; +} diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixB.ts b/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixB.ts new file mode 100644 index 00000000000..cdb26f8f614 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/Types/tstSuffixB.ts @@ -0,0 +1,3 @@ +export function resolvedFile(): 'tstSuffixB.ts' { + return 'tstSuffixB.ts'; +} diff --git a/javascript/ql/test/library-tests/frameworks/Express/MiddlewareFlow.qll b/javascript/ql/test/library-tests/frameworks/Express/MiddlewareFlow.qll index 76a21469f6b..9578d5139b5 100644 --- a/javascript/ql/test/library-tests/frameworks/Express/MiddlewareFlow.qll +++ b/javascript/ql/test/library-tests/frameworks/Express/MiddlewareFlow.qll @@ -1,3 +1,5 @@ import javascript -query DataFlow::Node dbUse() { result = API::moduleImport("@example/db").getInstance().getAUse() } +query DataFlow::Node dbUse() { + result = API::moduleImport("@example/db").getInstance().getAValueReachableFromSource() +} diff --git a/javascript/ql/test/library-tests/frameworks/data/test.ql b/javascript/ql/test/library-tests/frameworks/data/test.ql index bdc08e3706f..ff385f3afff 100644 --- a/javascript/ql/test/library-tests/frameworks/data/test.ql +++ b/javascript/ql/test/library-tests/frameworks/data/test.ql @@ -62,13 +62,13 @@ class BasicTaintTracking extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source.(DataFlow::CallNode).getCalleeName() = "source" or - source = ModelOutput::getASourceNode("test-source").getAnImmediateUse() + source = ModelOutput::getASourceNode("test-source").asSource() } override predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument() or - sink = ModelOutput::getASinkNode("test-sink").getARhs() + sink = ModelOutput::getASinkNode("test-sink").asSink() } } @@ -77,7 +77,7 @@ query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) { } query predicate isSink(DataFlow::Node node, string kind) { - node = ModelOutput::getASinkNode(kind).getARhs() + node = ModelOutput::getASinkNode(kind).asSink() } class SyntaxErrorTest extends ModelInput::SinkModelCsv { diff --git a/javascript/ql/test/query-tests/Declarations/UnusedProperty/tst.js b/javascript/ql/test/query-tests/Declarations/UnusedProperty/tst.js index 26cd1a110bb..847f30bd944 100644 --- a/javascript/ql/test/query-tests/Declarations/UnusedProperty/tst.js +++ b/javascript/ql/test/query-tests/Declarations/UnusedProperty/tst.js @@ -81,3 +81,11 @@ (function(){ ({ unusedProp: 42 }, 42); }); + +(function(){ + var foo = { + unused: 42 + }; + foo.unused = 42; + Object.hasOwn(foo, blab); +}); diff --git a/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected b/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected index 9db0d903c2d..73be1f62b89 100644 --- a/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected +++ b/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected @@ -5,6 +5,7 @@ | eval.js:19:9:19:24 | not_used_by_eval | Unused variable not_used_by_eval. | | externs.js:6:5:6:13 | iAmUnused | Unused variable iAmUnused. | | importWithoutPragma.jsx:1:1:1:27 | import ... react'; | Unused import h. | +| interTypes.ts:1:1:1:37 | import ... where"; | Unused import Bar. | | multi-imports.js:1:1:1:29 | import ... om 'x'; | Unused imports a, b, d. | | multi-imports.js:2:1:2:42 | import ... om 'x'; | Unused imports alphabetically, ordered. | | namespaceImportAsType.ts:3:1:3:23 | import ... om "z"; | Unused import Z. | diff --git a/javascript/ql/test/query-tests/Declarations/UnusedVariable/interTypes.ts b/javascript/ql/test/query-tests/Declarations/UnusedVariable/interTypes.ts new file mode 100644 index 00000000000..bdcd767fae8 --- /dev/null +++ b/javascript/ql/test/query-tests/Declarations/UnusedVariable/interTypes.ts @@ -0,0 +1,6 @@ +import { Foo, Bar } from "somewhere"; // OK + +type FooBar = + T extends [infer S extends Foo, ...unknown[]] + ? S + : never; diff --git a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected index a4654b29c27..5fa8a043558 100644 --- a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected +++ b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected @@ -50,6 +50,13 @@ nodes | express.js:146:16:146:24 | query.foo | | express.js:146:16:146:24 | query.foo | | express.js:146:16:146:24 | query.foo | +| express.js:150:7:150:34 | target | +| express.js:150:16:150:34 | req.param("target") | +| express.js:150:16:150:34 | req.param("target") | +| express.js:155:18:155:23 | target | +| express.js:155:18:155:23 | target | +| express.js:160:18:160:23 | target | +| express.js:160:18:160:23 | target | | koa.js:6:6:6:27 | url | | koa.js:6:12:6:27 | ctx.query.target | | koa.js:6:12:6:27 | ctx.query.target | @@ -140,6 +147,12 @@ edges | express.js:136:22:136:36 | req.params.user | express.js:136:16:136:36 | 'u' + r ... ms.user | | express.js:143:16:143:28 | req.query.foo | express.js:143:16:143:28 | req.query.foo | | express.js:146:16:146:24 | query.foo | express.js:146:16:146:24 | query.foo | +| express.js:150:7:150:34 | target | express.js:155:18:155:23 | target | +| express.js:150:7:150:34 | target | express.js:155:18:155:23 | target | +| express.js:150:7:150:34 | target | express.js:160:18:160:23 | target | +| express.js:150:7:150:34 | target | express.js:160:18:160:23 | target | +| express.js:150:16:150:34 | req.param("target") | express.js:150:7:150:34 | target | +| express.js:150:16:150:34 | req.param("target") | express.js:150:7:150:34 | target | | koa.js:6:6:6:27 | url | koa.js:7:15:7:17 | url | | koa.js:6:6:6:27 | url | koa.js:7:15:7:17 | url | | koa.js:6:6:6:27 | url | koa.js:8:18:8:20 | url | @@ -199,6 +212,8 @@ edges | express.js:136:16:136:36 | 'u' + r ... ms.user | express.js:136:22:136:36 | req.params.user | express.js:136:16:136:36 | 'u' + r ... ms.user | Untrusted URL redirection due to $@. | express.js:136:22:136:36 | req.params.user | user-provided value | | express.js:143:16:143:28 | req.query.foo | express.js:143:16:143:28 | req.query.foo | express.js:143:16:143:28 | req.query.foo | Untrusted URL redirection due to $@. | express.js:143:16:143:28 | req.query.foo | user-provided value | | express.js:146:16:146:24 | query.foo | express.js:146:16:146:24 | query.foo | express.js:146:16:146:24 | query.foo | Untrusted URL redirection due to $@. | express.js:146:16:146:24 | query.foo | user-provided value | +| express.js:155:18:155:23 | target | express.js:150:16:150:34 | req.param("target") | express.js:155:18:155:23 | target | Untrusted URL redirection due to $@. | express.js:150:16:150:34 | req.param("target") | user-provided value | +| express.js:160:18:160:23 | target | express.js:150:16:150:34 | req.param("target") | express.js:160:18:160:23 | target | Untrusted URL redirection due to $@. | express.js:150:16:150:34 | req.param("target") | user-provided value | | koa.js:7:15:7:17 | url | koa.js:6:12:6:27 | ctx.query.target | koa.js:7:15:7:17 | url | Untrusted URL redirection due to $@. | koa.js:6:12:6:27 | ctx.query.target | user-provided value | | koa.js:8:15:8:26 | `${url}${x}` | koa.js:6:12:6:27 | ctx.query.target | koa.js:8:15:8:26 | `${url}${x}` | Untrusted URL redirection due to $@. | koa.js:6:12:6:27 | ctx.query.target | user-provided value | | koa.js:14:16:14:18 | url | koa.js:6:12:6:27 | ctx.query.target | koa.js:14:16:14:18 | url | Untrusted URL redirection due to $@. | koa.js:6:12:6:27 | ctx.query.target | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js index b319315b985..667d6f89f05 100644 --- a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js +++ b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js @@ -144,4 +144,18 @@ app.get("foo", (req, res) => { }); app.get("bar", ({query}, res) => { res.redirect(query.foo); // NOT OK -}) \ No newline at end of file +}) + +app.get('/some/path', function(req, res) { + let target = req.param("target"); + + if (SAFE_TARGETS.hasOwnProperty(target)) + res.redirect(target); // OK: request parameter is checked against whitelist + else + res.redirect(target); // NOT OK + + if (Object.hasOwn(SAFE_TARGETS, target)) + res.redirect(target); // OK: request parameter is checked against whitelist + else + res.redirect(target); // NOT OK +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-843/TypeConfusionThroughParameterTampering.expected b/javascript/ql/test/query-tests/Security/CWE-843/TypeConfusionThroughParameterTampering.expected index 5c560d8c4ca..997d8968fcc 100644 --- a/javascript/ql/test/query-tests/Security/CWE-843/TypeConfusionThroughParameterTampering.expected +++ b/javascript/ql/test/query-tests/Security/CWE-843/TypeConfusionThroughParameterTampering.expected @@ -32,6 +32,18 @@ nodes | tst.js:81:9:81:9 | p | | tst.js:82:9:82:9 | p | | tst.js:82:9:82:9 | p | +| tst.js:90:5:90:12 | data.foo | +| tst.js:90:5:90:12 | data.foo | +| tst.js:90:5:90:12 | data.foo | +| tst.js:92:9:92:16 | data.foo | +| tst.js:92:9:92:16 | data.foo | +| tst.js:92:9:92:16 | data.foo | +| tst.js:95:9:95:16 | data.foo | +| tst.js:95:9:95:16 | data.foo | +| tst.js:95:9:95:16 | data.foo | +| tst.js:98:9:98:16 | data.foo | +| tst.js:98:9:98:16 | data.foo | +| tst.js:98:9:98:16 | data.foo | edges | tst.js:5:9:5:27 | foo | tst.js:6:5:6:7 | foo | | tst.js:5:9:5:27 | foo | tst.js:6:5:6:7 | foo | @@ -63,6 +75,10 @@ edges | tst.js:80:23:80:23 | p | tst.js:81:9:81:9 | p | | tst.js:80:23:80:23 | p | tst.js:82:9:82:9 | p | | tst.js:80:23:80:23 | p | tst.js:82:9:82:9 | p | +| tst.js:90:5:90:12 | data.foo | tst.js:90:5:90:12 | data.foo | +| tst.js:92:9:92:16 | data.foo | tst.js:92:9:92:16 | data.foo | +| tst.js:95:9:95:16 | data.foo | tst.js:95:9:95:16 | data.foo | +| tst.js:98:9:98:16 | data.foo | tst.js:98:9:98:16 | data.foo | #select | tst.js:6:5:6:7 | foo | tst.js:5:15:5:27 | req.query.foo | tst.js:6:5:6:7 | foo | Potential type confusion as $@ may be either an array or a string. | tst.js:5:15:5:27 | req.query.foo | this HTTP request parameter | | tst.js:8:5:8:7 | foo | tst.js:5:15:5:27 | req.query.foo | tst.js:8:5:8:7 | foo | Potential type confusion as $@ may be either an array or a string. | tst.js:5:15:5:27 | req.query.foo | this HTTP request parameter | @@ -75,3 +91,7 @@ edges | tst.js:46:5:46:7 | foo | tst.js:45:15:45:35 | ctx.req ... ery.foo | tst.js:46:5:46:7 | foo | Potential type confusion as $@ may be either an array or a string. | tst.js:45:15:45:35 | ctx.req ... ery.foo | this HTTP request parameter | | tst.js:81:9:81:9 | p | tst.js:77:25:77:38 | req.query.path | tst.js:81:9:81:9 | p | Potential type confusion as $@ may be either an array or a string. | tst.js:77:25:77:38 | req.query.path | this HTTP request parameter | | tst.js:82:9:82:9 | p | tst.js:77:25:77:38 | req.query.path | tst.js:82:9:82:9 | p | Potential type confusion as $@ may be either an array or a string. | tst.js:77:25:77:38 | req.query.path | this HTTP request parameter | +| tst.js:90:5:90:12 | data.foo | tst.js:90:5:90:12 | data.foo | tst.js:90:5:90:12 | data.foo | Potential type confusion as $@ may be either an array or a string. | tst.js:90:5:90:12 | data.foo | this HTTP request parameter | +| tst.js:92:9:92:16 | data.foo | tst.js:92:9:92:16 | data.foo | tst.js:92:9:92:16 | data.foo | Potential type confusion as $@ may be either an array or a string. | tst.js:92:9:92:16 | data.foo | this HTTP request parameter | +| tst.js:95:9:95:16 | data.foo | tst.js:95:9:95:16 | data.foo | tst.js:95:9:95:16 | data.foo | Potential type confusion as $@ may be either an array or a string. | tst.js:95:9:95:16 | data.foo | this HTTP request parameter | +| tst.js:98:9:98:16 | data.foo | tst.js:98:9:98:16 | data.foo | tst.js:98:9:98:16 | data.foo | Potential type confusion as $@ may be either an array or a string. | tst.js:98:9:98:16 | data.foo | this HTTP request parameter | diff --git a/javascript/ql/test/query-tests/Security/CWE-843/tst.js b/javascript/ql/test/query-tests/Security/CWE-843/tst.js index 82650bcf054..d49f7ce53d2 100644 --- a/javascript/ql/test/query-tests/Security/CWE-843/tst.js +++ b/javascript/ql/test/query-tests/Security/CWE-843/tst.js @@ -1,7 +1,7 @@ var express = require('express'); var Koa = require('koa'); -express().get('/some/path', function(req, res) { +express().get('/some/path', function (req, res) { var foo = req.query.foo; foo.indexOf(); // NOT OK @@ -41,38 +41,38 @@ express().get('/some/path', function(req, res) { foo.length; // NOT OK }); -new Koa().use(function handler(ctx){ +new Koa().use(function handler(ctx) { var foo = ctx.request.query.foo; foo.indexOf(); // NOT OK }); -express().get('/some/path/:foo', function(req, res) { +express().get('/some/path/:foo', function (req, res) { var foo = req.params.foo; foo.indexOf(); // OK }); -express().get('/some/path/:foo', function(req, res) { - if (req.query.path.length) {} // OK +express().get('/some/path/:foo', function (req, res) { + if (req.query.path.length) { } // OK req.query.path.length == 0; // OK !req.query.path.length; // OK req.query.path.length > 0; // OK }); -express().get('/some/path/:foo', function(req, res) { +express().get('/some/path/:foo', function (req, res) { let p = req.query.path; if (typeof p !== 'string') { - return; + return; } while (p.length) { // OK - p = p.substr(1); + p = p.substr(1); } p.length < 1; // OK }); -express().get('/some/path/:foo', function(req, res) { +express().get('/some/path/:foo', function (req, res) { let someObject = {}; safeGet(someObject, req.query.path).bar = 'baz'; // prototype pollution here - but flagged in `safeGet` }); @@ -84,3 +84,26 @@ function safeGet(obj, p) { } return obj[p]; } + +express().get('/foo', function (req, res) { + let data = req.query; + data.foo.indexOf(); // NOT OK + if (typeof data.foo !== 'undefined') { + data.foo.indexOf(); // NOT OK + } + if (typeof data.foo !== 'string') { + data.foo.indexOf(); // OK + } + if (typeof data.foo !== 'undefined') { + data.foo.indexOf(); // NOT OK + } +}); + +express().get('/foo', function (req, res) { + let data = req.query; + if (Array.isArray(data)) { + data.indexOf(); // OK + } else { + data.indexOf(); // OK + } +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/PrototypePollutingFunction.expected b/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/PrototypePollutingFunction.expected index 23b21f12460..f16f3e4ef55 100644 --- a/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/PrototypePollutingFunction.expected +++ b/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/PrototypePollutingFunction.expected @@ -1478,6 +1478,56 @@ nodes | tests.js:547:24:547:28 | value | | tests.js:547:24:547:28 | value | | tests.js:547:24:547:28 | value | +| tests.js:552:35:552:37 | src | +| tests.js:552:35:552:37 | src | +| tests.js:553:14:553:16 | key | +| tests.js:553:14:553:16 | key | +| tests.js:553:14:553:16 | key | +| tests.js:557:43:557:45 | src | +| tests.js:557:43:557:45 | src | +| tests.js:557:43:557:50 | src[key] | +| tests.js:557:43:557:50 | src[key] | +| tests.js:557:43:557:50 | src[key] | +| tests.js:557:43:557:50 | src[key] | +| tests.js:557:43:557:50 | src[key] | +| tests.js:559:17:559:19 | key | +| tests.js:559:17:559:19 | key | +| tests.js:559:17:559:19 | key | +| tests.js:559:24:559:26 | src | +| tests.js:559:24:559:26 | src | +| tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:31 | src[key] | +| tests.js:559:28:559:30 | key | +| tests.js:559:28:559:30 | key | +| tests.js:564:35:564:37 | src | +| tests.js:564:35:564:37 | src | +| tests.js:565:14:565:16 | key | +| tests.js:565:14:565:16 | key | +| tests.js:565:14:565:16 | key | +| tests.js:569:43:569:45 | src | +| tests.js:569:43:569:45 | src | +| tests.js:569:43:569:50 | src[key] | +| tests.js:569:43:569:50 | src[key] | +| tests.js:569:43:569:50 | src[key] | +| tests.js:569:43:569:50 | src[key] | +| tests.js:569:43:569:50 | src[key] | +| tests.js:571:17:571:19 | key | +| tests.js:571:17:571:19 | key | +| tests.js:571:17:571:19 | key | +| tests.js:571:24:571:26 | src | +| tests.js:571:24:571:26 | src | +| tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:31 | src[key] | +| tests.js:571:28:571:30 | key | +| tests.js:571:28:571:30 | key | edges | examples/PrototypePollutingFunction.js:1:16:1:18 | dst | examples/PrototypePollutingFunction.js:5:19:5:21 | dst | | examples/PrototypePollutingFunction.js:1:16:1:18 | dst | examples/PrototypePollutingFunction.js:5:19:5:21 | dst | @@ -3347,6 +3397,70 @@ edges | tests.js:545:43:545:47 | value | tests.js:542:35:542:37 | src | | tests.js:545:43:545:47 | value | tests.js:542:35:542:37 | src | | tests.js:545:43:545:47 | value | tests.js:542:35:542:37 | src | +| tests.js:552:35:552:37 | src | tests.js:557:43:557:45 | src | +| tests.js:552:35:552:37 | src | tests.js:557:43:557:45 | src | +| tests.js:552:35:552:37 | src | tests.js:559:24:559:26 | src | +| tests.js:552:35:552:37 | src | tests.js:559:24:559:26 | src | +| tests.js:553:14:553:16 | key | tests.js:559:17:559:19 | key | +| tests.js:553:14:553:16 | key | tests.js:559:17:559:19 | key | +| tests.js:553:14:553:16 | key | tests.js:559:17:559:19 | key | +| tests.js:553:14:553:16 | key | tests.js:559:17:559:19 | key | +| tests.js:553:14:553:16 | key | tests.js:559:17:559:19 | key | +| tests.js:553:14:553:16 | key | tests.js:559:17:559:19 | key | +| tests.js:553:14:553:16 | key | tests.js:559:17:559:19 | key | +| tests.js:553:14:553:16 | key | tests.js:559:28:559:30 | key | +| tests.js:553:14:553:16 | key | tests.js:559:28:559:30 | key | +| tests.js:553:14:553:16 | key | tests.js:559:28:559:30 | key | +| tests.js:553:14:553:16 | key | tests.js:559:28:559:30 | key | +| tests.js:557:43:557:45 | src | tests.js:557:43:557:50 | src[key] | +| tests.js:557:43:557:45 | src | tests.js:557:43:557:50 | src[key] | +| tests.js:557:43:557:50 | src[key] | tests.js:552:35:552:37 | src | +| tests.js:557:43:557:50 | src[key] | tests.js:552:35:552:37 | src | +| tests.js:557:43:557:50 | src[key] | tests.js:552:35:552:37 | src | +| tests.js:557:43:557:50 | src[key] | tests.js:552:35:552:37 | src | +| tests.js:557:43:557:50 | src[key] | tests.js:552:35:552:37 | src | +| tests.js:557:43:557:50 | src[key] | tests.js:552:35:552:37 | src | +| tests.js:559:24:559:26 | src | tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:26 | src | tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:26 | src | tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:26 | src | tests.js:559:24:559:31 | src[key] | +| tests.js:559:24:559:31 | src[key] | tests.js:559:24:559:31 | src[key] | +| tests.js:559:28:559:30 | key | tests.js:559:24:559:31 | src[key] | +| tests.js:559:28:559:30 | key | tests.js:559:24:559:31 | src[key] | +| tests.js:559:28:559:30 | key | tests.js:559:24:559:31 | src[key] | +| tests.js:559:28:559:30 | key | tests.js:559:24:559:31 | src[key] | +| tests.js:564:35:564:37 | src | tests.js:569:43:569:45 | src | +| tests.js:564:35:564:37 | src | tests.js:569:43:569:45 | src | +| tests.js:564:35:564:37 | src | tests.js:571:24:571:26 | src | +| tests.js:564:35:564:37 | src | tests.js:571:24:571:26 | src | +| tests.js:565:14:565:16 | key | tests.js:571:17:571:19 | key | +| tests.js:565:14:565:16 | key | tests.js:571:17:571:19 | key | +| tests.js:565:14:565:16 | key | tests.js:571:17:571:19 | key | +| tests.js:565:14:565:16 | key | tests.js:571:17:571:19 | key | +| tests.js:565:14:565:16 | key | tests.js:571:17:571:19 | key | +| tests.js:565:14:565:16 | key | tests.js:571:17:571:19 | key | +| tests.js:565:14:565:16 | key | tests.js:571:17:571:19 | key | +| tests.js:565:14:565:16 | key | tests.js:571:28:571:30 | key | +| tests.js:565:14:565:16 | key | tests.js:571:28:571:30 | key | +| tests.js:565:14:565:16 | key | tests.js:571:28:571:30 | key | +| tests.js:565:14:565:16 | key | tests.js:571:28:571:30 | key | +| tests.js:569:43:569:45 | src | tests.js:569:43:569:50 | src[key] | +| tests.js:569:43:569:45 | src | tests.js:569:43:569:50 | src[key] | +| tests.js:569:43:569:50 | src[key] | tests.js:564:35:564:37 | src | +| tests.js:569:43:569:50 | src[key] | tests.js:564:35:564:37 | src | +| tests.js:569:43:569:50 | src[key] | tests.js:564:35:564:37 | src | +| tests.js:569:43:569:50 | src[key] | tests.js:564:35:564:37 | src | +| tests.js:569:43:569:50 | src[key] | tests.js:564:35:564:37 | src | +| tests.js:569:43:569:50 | src[key] | tests.js:564:35:564:37 | src | +| tests.js:571:24:571:26 | src | tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:26 | src | tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:26 | src | tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:26 | src | tests.js:571:24:571:31 | src[key] | +| tests.js:571:24:571:31 | src[key] | tests.js:571:24:571:31 | src[key] | +| tests.js:571:28:571:30 | key | tests.js:571:24:571:31 | src[key] | +| tests.js:571:28:571:30 | key | tests.js:571:24:571:31 | src[key] | +| tests.js:571:28:571:30 | key | tests.js:571:24:571:31 | src[key] | +| tests.js:571:28:571:30 | key | tests.js:571:24:571:31 | src[key] | #select | examples/PrototypePollutingFunction.js:7:13:7:15 | dst | examples/PrototypePollutingFunction.js:2:14:2:16 | key | examples/PrototypePollutingFunction.js:7:13:7:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | examples/PrototypePollutingFunction.js:2:21:2:23 | src | src | examples/PrototypePollutingFunction.js:7:13:7:15 | dst | dst | | path-assignment.js:15:13:15:18 | target | path-assignment.js:8:19:8:25 | keys[i] | path-assignment.js:15:13:15:18 | target | The property chain $@ is recursively assigned to $@ without guarding against prototype pollution. | path-assignment.js:8:19:8:25 | keys[i] | here | path-assignment.js:15:13:15:18 | target | target | diff --git a/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/tests.js b/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/tests.js index d54081d6e7d..a1fc92a5776 100644 --- a/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/tests.js +++ b/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingFunction/tests.js @@ -548,3 +548,27 @@ function mergeUsingCallback3(dst, src) { } }); } + +function copyHasOwnProperty2(dst, src) { + for (let key in src) { + // Guarding the recursive case by dst.hasOwnProperty (or Object.hasOwn) is safe, + // since '__proto__' and 'constructor' are not own properties of the destination object. + if (Object.hasOwn(dst, key)) { + copyHasOwnProperty2(dst[key], src[key]); + } else { + dst[key] = src[key]; // OK + } + } +} + +function copyHasOwnProperty3(dst, src) { + for (let key in src) { + // Guarding the recursive case by dst.hasOwnProperty (or Object.hasOwn) is safe, + // since '__proto__' and 'constructor' are not own properties of the destination object. + if (_.has(dst, key)) { + copyHasOwnProperty3(dst[key], src[key]); + } else { + dst[key] = src[key]; // OK + } + } +} diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 973e1ea85fa..fcb89e5f866 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -136,6 +136,9 @@ module API { result = this.getASuccessor(Label::keywordParameter(name)) } + /** Gets the node representing the self parameter */ + Node getSelfParameter() { result = this.getASuccessor(Label::selfParameter()) } + /** * Gets the number of parameters of the function represented by this node. */ @@ -321,6 +324,12 @@ module API { /** Gets the API node for a parameter of this invocation. */ Node getAParameter() { result = this.getParameter(_) } + /** Gets the object that this method-call is being called on, if this is a method-call */ + Node getSelfParameter() { + result.getARhs() = this.(DataFlow::MethodCallNode).getObject() and + result = callee.getSelfParameter() + } + /** Gets the API node for the keyword parameter `name` of this invocation. */ Node getKeywordParameter(string name) { result = callee.getKeywordParameter(name) and @@ -345,6 +354,14 @@ module API { result = callee.getReturn() and result.getAnImmediateUse() = this } + + /** + * Gets the number of positional arguments of this call. + * + * Note: This is used for `WithArity[]` in modeling-as-data, where we thought + * including keyword arguments didn't make much sense. + */ + int getNumArgument() { result = count(this.getArg(_)) } } /** @@ -589,15 +606,24 @@ module API { exists(DataFlow::Node def, PY::CallableExpr fn | rhs(base, def) and fn = trackDefNode(def).asExpr() | - exists(int i | - lbl = Label::parameter(i) and + exists(int i, int offset | + if exists(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf()) + then offset = 1 + else offset = 0 + | + lbl = Label::parameter(i - offset) and ref.asExpr() = fn.getInnerScope().getArg(i) ) or - exists(string name | + exists(string name, PY::Parameter param | lbl = Label::keywordParameter(name) and - ref.asExpr() = fn.getInnerScope().getArgByName(name) + param = fn.getInnerScope().getArgByName(name) and + not param.isSelf() and + ref.asExpr() = param ) + or + lbl = Label::selfParameter() and + ref.asExpr() = any(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf()) ) or // Built-ins, treated as members of the module `builtins` @@ -664,6 +690,9 @@ module API { exists(string name | lbl = Label::keywordParameter(name) | arg = pred.getACall().getArgByName(name) ) + or + lbl = Label::selfParameter() and + arg = pred.getACall().(DataFlow::MethodCallNode).getObject() ) } @@ -780,6 +809,7 @@ module API { or exists(any(PY::Function f).getArgByName(name)) } or + MkLabelSelfParameter() or MkLabelReturn() or MkLabelSubclass() or MkLabelAwait() @@ -837,6 +867,11 @@ module API { string getName() { result = name } } + /** A label for the self parameter. */ + class LabelSelfParameter extends ApiLabel, MkLabelSelfParameter { + override string toString() { result = "getSelfParameter()" } + } + /** A label that gets the return value of a function. */ class LabelReturn extends ApiLabel, MkLabelReturn { override string toString() { result = "getReturn()" } @@ -876,6 +911,9 @@ module API { /** Gets the `parameter` edge label for the keyword parameter `name`. */ LabelKeywordParameter keywordParameter(string name) { result.getName() = name } + /** Gets the edge label for the self parameter. */ + LabelSelfParameter selfParameter() { any() } + /** Gets the `return` edge label. */ LabelReturn return() { any() } diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index 4812628d262..daa67ee4231 100644 --- a/python/ql/lib/semmle/python/Frameworks.qll +++ b/python/ql/lib/semmle/python/Frameworks.qll @@ -12,6 +12,7 @@ private import semmle.python.frameworks.Asyncpg private import semmle.python.frameworks.ClickhouseDriver private import semmle.python.frameworks.Cryptodome private import semmle.python.frameworks.Cryptography +private import semmle.python.frameworks.data.ModelsAsData private import semmle.python.frameworks.Dill private import semmle.python.frameworks.Django private import semmle.python.frameworks.Fabric diff --git a/python/ql/lib/semmle/python/filters/Tests.qll b/python/ql/lib/semmle/python/filters/Tests.qll index 53c4e1ba176..44ea2edebe4 100644 --- a/python/ql/lib/semmle/python/filters/Tests.qll +++ b/python/ql/lib/semmle/python/filters/Tests.qll @@ -1,14 +1,15 @@ import python +private import semmle.python.ApiGraphs abstract class TestScope extends Scope { } -// don't extend Class directly to avoid ambiguous method warnings -class UnitTestClass extends TestScope { +class UnitTestClass extends TestScope, Class { UnitTestClass() { - exists(ClassValue cls | this = cls.getScope() | - cls.getABaseType+() = Module::named("unittest").attr(_) - or - cls.getABaseType+().getName().toLowerCase() = "testcase" + exists(API::Node testCaseClass, string testCaseString | + testCaseString.matches("%TestCase") and + testCaseClass = any(API::Node mod).getMember(testCaseString) + | + this.getParent() = testCaseClass.getASubclass*().getAnImmediateUse().asExpr() ) } } diff --git a/python/ql/lib/semmle/python/frameworks/Asyncpg.qll b/python/ql/lib/semmle/python/frameworks/Asyncpg.qll index 5f867fe28ff..81da12a015c 100644 --- a/python/ql/lib/semmle/python/frameworks/Asyncpg.qll +++ b/python/ql/lib/semmle/python/frameworks/Asyncpg.qll @@ -7,91 +7,42 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import semmle.python.ApiGraphs +private import semmle.python.frameworks.data.ModelsAsData /** Provides models for the `asyncpg` PyPI package. */ private module Asyncpg { - private import semmle.python.internal.Awaited - - /** Gets a `ConnectionPool` that is created when the result of `asyncpg.create_pool()` is awaited. */ - API::Node connectionPool() { - result = API::moduleImport("asyncpg").getMember("create_pool").getReturn().getAwaited() - } - - /** - * Gets a `Connection` that is created when - * - the result of `asyncpg.connect()` is awaited. - * - the result of calling `acquire` on a `ConnectionPool` is awaited. - */ - API::Node connection() { - result = API::moduleImport("asyncpg").getMember("connect").getReturn().getAwaited() - or - result = connectionPool().getMember("acquire").getReturn().getAwaited() - } - - /** `Connection`s and `ConnectionPool`s provide some methods that execute SQL. */ - class SqlExecutionOnConnection extends SqlExecution::Range, DataFlow::MethodCallNode { - string methodName; - - SqlExecutionOnConnection() { - this = [connectionPool(), connection()].getMember(methodName).getACall() and - methodName in ["copy_from_query", "execute", "fetch", "fetchrow", "fetchval", "executemany"] - } - - override DataFlow::Node getSql() { - methodName in ["copy_from_query", "execute", "fetch", "fetchrow", "fetchval"] and - result in [this.getArg(0), this.getArgByName("query")] - or - methodName = "executemany" and - result in [this.getArg(0), this.getArgByName("command")] + class AsyncpgModel extends ModelInput::TypeModelCsv { + override predicate row(string row) { + // package1;type1;package2;type2;path + row = + [ + // a `ConnectionPool` that is created when the result of `asyncpg.create_pool()` is awaited. + "asyncpg;ConnectionPool;asyncpg;;Member[create_pool].ReturnValue.Awaited", + // a `Connection` that is created when + // * - the result of `asyncpg.connect()` is awaited. + // * - the result of calling `acquire` on a `ConnectionPool` is awaited. + "asyncpg;Connection;asyncpg;;Member[connect].ReturnValue.Awaited", + "asyncpg;Connection;asyncpg;ConnectionPool;Member[acquire].ReturnValue.Awaited", + // Creating an internal `~Connection` type that contains both `Connection` and `ConnectionPool`. + "asyncpg;~Connection;asyncpg;Connection;", "asyncpg;~Connection;asyncpg;ConnectionPool;" + ] } } - /** A model of `Connection` and `ConnectionPool`, which provide some methods that access the file system. */ - class FileAccessOnConnection extends FileSystemAccess::Range, DataFlow::MethodCallNode { - string methodName; - - FileAccessOnConnection() { - this = [connectionPool(), connection()].getMember(methodName).getACall() and - methodName in ["copy_from_query", "copy_from_table", "copy_to_table"] - } - - // The path argument is keyword only. - override DataFlow::Node getAPathArgument() { - methodName in ["copy_from_query", "copy_from_table"] and - result = this.getArgByName("output") - or - methodName = "copy_to_table" and - result = this.getArgByName("source") - } - } - - /** - * Provides models of the `PreparedStatement` class in `asyncpg`. - * `PreparedStatement`s are created when the result of calling `prepare(query)` on a connection is awaited. - * The result of calling `prepare(query)` is a `PreparedStatementFactory` and the argument, `query` needs to - * be tracked to the place where a `PreparedStatement` is created and then further to any executing methods. - * Hence the two type trackers. - */ - module PreparedStatement { - class PreparedStatementConstruction extends SqlConstruction::Range, API::CallNode { - PreparedStatementConstruction() { this = connection().getMember("prepare").getACall() } - - override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() } - } - - class PreparedStatementExecution extends SqlExecution::Range, API::CallNode { - PreparedStatementConstruction prepareCall; - - PreparedStatementExecution() { - this = - prepareCall - .getReturn() - .getAwaited() - .getMember(["executemany", "fetch", "fetchrow", "fetchval"]) - .getACall() - } - - override DataFlow::Node getSql() { result = prepareCall.getSql() } + class AsyncpgSink extends ModelInput::SinkModelCsv { + // package;type;path;kind + override predicate row(string row) { + row = + [ + // `Connection`s and `ConnectionPool`s provide some methods that execute SQL. + "asyncpg;~Connection;Member[copy_from_query,execute,fetch,fetchrow,fetchval].Argument[0,query:];sql-injection", + "asyncpg;~Connection;Member[executemany].Argument[0,command:];sql-injection", + // A model of `Connection` and `ConnectionPool`, which provide some methods that access the file system. + "asyncpg;~Connection;Member[copy_from_query,copy_from_table].Argument[output:];path-injection", + "asyncpg;~Connection;Member[copy_to_table].Argument[source:];path-injection", + // the `PreparedStatement` class in `asyncpg`. + "asyncpg;Connection;Member[prepare].Argument[0,query:];sql-injection", + ] } } @@ -106,7 +57,9 @@ private module Asyncpg { */ module Cursor { class CursorConstruction extends SqlConstruction::Range, API::CallNode { - CursorConstruction() { this = connection().getMember("cursor").getACall() } + CursorConstruction() { + this = ModelOutput::getATypeNode("asyncpg", "Connection").getMember("cursor").getACall() + } override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() } } @@ -121,8 +74,11 @@ private module Asyncpg { this = c.getReturn().getAwaited().getAnImmediateUse() ) or - exists(PreparedStatement::PreparedStatementConstruction prepareCall | - sql = prepareCall.getSql() and + exists(API::CallNode prepareCall | + prepareCall = + ModelOutput::getATypeNode("asyncpg", "Connection").getMember("prepare").getACall() + | + sql = prepareCall.getParameter(0, "query").getARhs() and this = prepareCall .getReturn() diff --git a/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll new file mode 100644 index 00000000000..2af91a69432 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll @@ -0,0 +1,47 @@ +/** + * Provides classes for contributing a model, or using the interpreted results + * of a model represented as data. + * + * - Use the `ModelInput` module to contribute new models. + * - Use the `ModelOutput` module to access the model results in terms of API nodes. + * + * The package name refers to the top-level module the import comes from, and not a PyPI package. + * So for `from foo.bar import baz`, the package will be `foo`. + */ + +private import python +private import internal.ApiGraphModels as Shared +private import internal.ApiGraphModelsSpecific as Specific +import Shared::ModelInput as ModelInput +import Shared::ModelOutput as ModelOutput +private import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.ApiGraphs +private import semmle.python.dataflow.new.TaintTracking + +/** + * A remote flow source originating from a CSV source row. + */ +private class RemoteFlowSourceFromCsv extends RemoteFlowSource { + RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() } + + override string getSourceType() { result = "Remote flow (from model)" } +} + +/** + * Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes. + */ +private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) { + exists(API::Node predNode, API::Node succNode | + Specific::summaryStep(predNode, succNode, kind) and + pred = predNode.getARhs() and + succ = succNode.getAnImmediateUse() + ) +} + +/** Taint steps induced by summary models of kind `taint`. */ +private class TaintStepFromSummary extends TaintTracking::AdditionalTaintStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + summaryStepNodes(pred, succ, "taint") + } +} diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/AccessPathSyntax.qll b/python/ql/lib/semmle/python/frameworks/data/internal/AccessPathSyntax.qll new file mode 100644 index 00000000000..076e12f2671 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/data/internal/AccessPathSyntax.qll @@ -0,0 +1,182 @@ +/** + * Module for parsing access paths from CSV models, both the identifying access path used + * by dynamic languages, and the input/output specifications for summary steps. + * + * This file is used by the shared data flow library and by the JavaScript libraries + * (which does not use the shared data flow libraries). + */ + +/** + * Convenience-predicate for extracting two capture groups at once. + */ +bindingset[input, regexp] +private predicate regexpCaptureTwo(string input, string regexp, string capture1, string capture2) { + capture1 = input.regexpCapture(regexp, 1) and + capture2 = input.regexpCapture(regexp, 2) +} + +/** Companion module to the `AccessPath` class. */ +module AccessPath { + /** A string that should be parsed as an access path. */ + abstract class Range extends string { + bindingset[this] + Range() { any() } + } + + /** + * Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value + * of the constant or any value contained in the interval. + */ + bindingset[arg] + int parseInt(string arg) { + result = arg.toInt() + or + // Match "n1..n2" + exists(string lo, string hi | + regexpCaptureTwo(arg, "(-?\\d+)\\.\\.(-?\\d+)", lo, hi) and + result = [lo.toInt() .. hi.toInt()] + ) + } + + /** + * Parses a lower-bounded interval `n..` and gets the lower bound. + */ + bindingset[arg] + int parseLowerBound(string arg) { result = arg.regexpCapture("(-?\\d+)\\.\\.", 1).toInt() } + + /** + * Parses an integer constant or interval (bounded or unbounded) that explicitly + * references the arity, such as `N-1` or `N-3..N-1`. + * + * Note that expressions of form `N-x` will never resolve to a negative index, + * even if `N` is zero (it will have no result in that case). + */ + bindingset[arg, arity] + private int parseIntWithExplicitArity(string arg, int arity) { + result >= 0 and // do not allow N-1 to resolve to a negative index + exists(string lo | + // N-x + lo = arg.regexpCapture("N-(\\d+)", 1) and + result = arity - lo.toInt() + or + // N-x.. + lo = arg.regexpCapture("N-(\\d+)\\.\\.", 1) and + result = [arity - lo.toInt(), arity - 1] + ) + or + exists(string lo, string hi | + // x..N-y + regexpCaptureTwo(arg, "(-?\\d+)\\.\\.N-(\\d+)", lo, hi) and + result = [lo.toInt() .. arity - hi.toInt()] + or + // N-x..N-y + regexpCaptureTwo(arg, "N-(\\d+)\\.\\.N-(\\d+)", lo, hi) and + result = [arity - lo.toInt() .. arity - hi.toInt()] and + result >= 0 + or + // N-x..y + regexpCaptureTwo(arg, "N-(\\d+)\\.\\.(\\d+)", lo, hi) and + result = [arity - lo.toInt() .. hi.toInt()] and + result >= 0 + ) + } + + /** + * Parses an integer constant or interval (bounded or unbounded) and gets any + * of the integers contained within (of which there may be infinitely many). + * + * Has no result for arguments involving an explicit arity, such as `N-1`. + */ + bindingset[arg, result] + int parseIntUnbounded(string arg) { + result = parseInt(arg) + or + result >= parseLowerBound(arg) + } + + /** + * Parses an integer constant or interval (bounded or unbounded) that + * may reference the arity of a call, such as `N-1` or `N-3..N-1`. + * + * Note that expressions of form `N-x` will never resolve to a negative index, + * even if `N` is zero (it will have no result in that case). + */ + bindingset[arg, arity] + int parseIntWithArity(string arg, int arity) { + result = parseInt(arg) + or + result in [parseLowerBound(arg) .. arity - 1] + or + result = parseIntWithExplicitArity(arg, arity) + } +} + +/** Gets the `n`th token on the access path as a string. */ +private string getRawToken(AccessPath path, int n) { + // Avoid splitting by '.' since tokens may contain dots, e.g. `Field[foo.Bar.x]`. + // Instead use regexpFind to match valid tokens, and supplement with a final length + // check (in `AccessPath.hasSyntaxError`) to ensure all characters were included in a token. + result = path.regexpFind("\\w+(?:\\[[^\\]]*\\])?(?=\\.|$)", n, _) +} + +/** + * A string that occurs as an access path (either identifying or input/output spec) + * which might be relevant for this database. + */ +class AccessPath extends string instanceof AccessPath::Range { + /** Holds if this string is not a syntactically valid access path. */ + predicate hasSyntaxError() { + // If the lengths match, all characters must haven been included in a token + // or seen by the `.` lookahead pattern. + this != "" and + not this.length() = sum(int n | | getRawToken(this, n).length() + 1) - 1 + } + + /** Gets the `n`th token on the access path (if there are no syntax errors). */ + AccessPathToken getToken(int n) { + result = getRawToken(this, n) and + not this.hasSyntaxError() + } + + /** Gets the number of tokens on the path (if there are no syntax errors). */ + int getNumToken() { + result = count(int n | exists(getRawToken(this, n))) and + not this.hasSyntaxError() + } +} + +/** + * An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths. + */ +class AccessPathToken extends string { + AccessPathToken() { this = getRawToken(_, _) } + + private string getPart(int part) { + result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part) + } + + /** Gets the name of the token, such as `Member` from `Member[x]` */ + string getName() { result = this.getPart(1) } + + /** + * Gets the argument list, such as `1,2` from `Member[1,2]`, + * or has no result if there are no arguments. + */ + string getArgumentList() { result = this.getPart(2) } + + /** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */ + string getArgument(int n) { result = this.getArgumentList().splitAt(",", n).trim() } + + /** Gets the `n`th argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ + pragma[nomagic] + string getArgument(string name, int n) { name = this.getName() and result = this.getArgument(n) } + + /** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */ + string getAnArgument() { result = this.getArgument(_) } + + /** Gets an argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ + string getAnArgument(string name) { result = this.getArgument(name, _) } + + /** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */ + int getNumArgument() { result = count(int n | exists(this.getArgument(n))) } +} diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll new file mode 100644 index 00000000000..69563a3eab4 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll @@ -0,0 +1,522 @@ +/** + * INTERNAL use only. This is an experimental API subject to change without notice. + * + * Provides classes and predicates for dealing with flow models specified in CSV format. + * + * The CSV specification has the following columns: + * - Sources: + * `package; type; path; kind` + * - Sinks: + * `package; type; path; kind` + * - Summaries: + * `package; type; path; input; output; kind` + * - Types: + * `package1; type1; package2; type2; path` + * + * The interpretation of a row is similar to API-graphs with a left-to-right + * reading. + * 1. The `package` column selects a package name, as it would be referenced in the source code, + * such as an NPM package, PIP package, or Ruby gem. (See `ModelsAsData.qll` for language-specific details). + * It may also be a synthetic package used for a type definition (see type definitions below). + * 2. The `type` column selects all instances of a named type originating from that package, + * or the empty string if referring to the package itself. + * It can also be a synthetic type name defined by a type definition (see type definitions below). + * 3. The `path` column is a `.`-separated list of "access path tokens" to resolve, starting at the node selected by `package` and `type`. + * + * Every language supports the following tokens: + * - Argument[n]: the n-th argument to a call. May be a range of form `x..y` (inclusive) and/or a comma-separated list. + * Additionally, `N-1` refers to the last argument, `N-2` refers to the second-last, and so on. + * - Parameter[n]: the n-th parameter of a callback. May be a range of form `x..y` (inclusive) and/or a comma-separated list. + * - ReturnValue: the value returned by a function call + * - WithArity[n]: match a call with the given arity. May be a range of form `x..y` (inclusive) and/or a comma-separated list. + * + * The following tokens are common and should be implemented for languages where it makes sense: + * - Member[x]: a member named `x`; exactly what a "member" is depends on the language. May be a comma-separated list of names. + * - Instance: an instance of a class + * - Subclass: a subclass of a class + * - ArrayElement: an element of array + * - Element: an element of a collection-like object + * - MapKey: a key in map-like object + * - MapValue: a value in a map-like object + * - Awaited: the value from a resolved promise/future-like object + * + * For the time being, please consult `ApiGraphModelsSpecific.qll` to see which language-specific tokens are currently supported. + * + * 4. The `input` and `output` columns specify how data enters and leaves the element selected by the + * first `(package, type, path)` tuple. Both strings are `.`-separated access paths + * of the same syntax as the `path` column. + * 5. The `kind` column is a tag that can be referenced from QL to determine to + * which classes the interpreted elements should be added. For example, for + * sources `"remote"` indicates a default remote flow source, and for summaries + * `"taint"` indicates a default additional taint step and `"value"` indicates a + * globally applicable value-preserving step. + * + * ### Types + * + * A type row of form `package1; type1; package2; type2; path` indicates that `package2; type2; path` + * should be seen as an instance of the type `package1; type1`. + * + * A `(package,type)` pair may refer to a static type or a synthetic type name used internally in the model. + * Synthetic type names can be used to reuse intermediate sub-paths, when there are multiple ways to access the same + * element. + * See `ModelsAsData.qll` for the language-specific interpretation of packages and static type names. + * + * By convention, if one wants to avoid clashes with static types from the package, the type name + * should be prefixed with a tilde character (`~`). For example, `(foo, ~Bar)` can be used to indicate that + * the type is related to the `foo` package but is not intended to match a static type. + */ + +private import ApiGraphModelsSpecific as Specific + +private class Unit = Specific::Unit; + +private module API = Specific::API; + +private import Specific::AccessPathSyntax + +/** Module containing hooks for providing input data to be interpreted as a model. */ +module ModelInput { + /** + * A unit class for adding additional source model rows. + * + * Extend this class to add additional source definitions. + */ + class SourceModelCsv extends Unit { + /** + * Holds if `row` specifies a source definition. + * + * A row of form + * ``` + * package;type;path;kind + * ``` + * indicates that the value at `(package, type, path)` should be seen as a flow + * source of the given `kind`. + * + * The kind `remote` represents a general remote flow source. + */ + abstract predicate row(string row); + } + + /** + * A unit class for adding additional sink model rows. + * + * Extend this class to add additional sink definitions. + */ + class SinkModelCsv extends Unit { + /** + * Holds if `row` specifies a sink definition. + * + * A row of form + * ``` + * package;type;path;kind + * ``` + * indicates that the value at `(package, type, path)` should be seen as a sink + * of the given `kind`. + */ + abstract predicate row(string row); + } + + /** + * A unit class for adding additional summary model rows. + * + * Extend this class to add additional flow summary definitions. + */ + class SummaryModelCsv extends Unit { + /** + * Holds if `row` specifies a summary definition. + * + * A row of form + * ``` + * package;type;path;input;output;kind + * ``` + * indicates that for each call to `(package, type, path)`, the value referred to by `input` + * can flow to the value referred to by `output`. + * + * `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps, + * respectively. + */ + abstract predicate row(string row); + } + + /** + * A unit class for adding additional type model rows. + * + * Extend this class to add additional type definitions. + */ + class TypeModelCsv extends Unit { + /** + * Holds if `row` specifies a type definition. + * + * A row of form, + * ``` + * package1;type1;package2;type2;path + * ``` + * indicates that `(package2, type2, path)` should be seen as an instance of `(package1, type1)`. + */ + abstract predicate row(string row); + } +} + +private import ModelInput + +/** + * An empty class, except in specific tests. + * + * If this is non-empty, all models are parsed even if the package is not + * considered relevant for the current database. + */ +abstract class TestAllModels extends Unit { } + +/** + * Append `;dummy` to the value of `s` to work around the fact that `string.split(delim,n)` + * does not preserve empty trailing substrings. + */ +bindingset[result] +private string inversePad(string s) { s = result + ";dummy" } + +private predicate sourceModel(string row) { any(SourceModelCsv s).row(inversePad(row)) } + +private predicate sinkModel(string row) { any(SinkModelCsv s).row(inversePad(row)) } + +private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inversePad(row)) } + +private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) } + +/** Holds if a source model exists for the given parameters. */ +predicate sourceModel(string package, string type, string path, string kind) { + exists(string row | + sourceModel(row) and + row.splitAt(";", 0) = package and + row.splitAt(";", 1) = type and + row.splitAt(";", 2) = path and + row.splitAt(";", 3) = kind + ) +} + +/** Holds if a sink model exists for the given parameters. */ +private predicate sinkModel(string package, string type, string path, string kind) { + exists(string row | + sinkModel(row) and + row.splitAt(";", 0) = package and + row.splitAt(";", 1) = type and + row.splitAt(";", 2) = path and + row.splitAt(";", 3) = kind + ) +} + +/** Holds if a summary model `row` exists for the given parameters. */ +private predicate summaryModel( + string package, string type, string path, string input, string output, string kind +) { + exists(string row | + summaryModel(row) and + row.splitAt(";", 0) = package and + row.splitAt(";", 1) = type and + row.splitAt(";", 2) = path and + row.splitAt(";", 3) = input and + row.splitAt(";", 4) = output and + row.splitAt(";", 5) = kind + ) +} + +/** Holds if an type model exists for the given parameters. */ +private predicate typeModel( + string package1, string type1, string package2, string type2, string path +) { + exists(string row | + typeModel(row) and + row.splitAt(";", 0) = package1 and + row.splitAt(";", 1) = type1 and + row.splitAt(";", 2) = package2 and + row.splitAt(";", 3) = type2 and + row.splitAt(";", 4) = path + ) +} + +/** + * Gets a package that should be seen as an alias for the given other `package`, + * or the `package` itself. + */ +bindingset[package] +bindingset[result] +string getAPackageAlias(string package) { + typeModel(package, "", result, "", "") + or + result = package +} + +/** + * Holds if CSV rows involving `package` might be relevant for the analysis of this database. + */ +private predicate isRelevantPackage(string package) { + ( + sourceModel(package, _, _, _) or + sinkModel(package, _, _, _) or + summaryModel(package, _, _, _, _, _) or + typeModel(package, _, _, _, _) + ) and + ( + Specific::isPackageUsed(package) + or + exists(TestAllModels t) + ) + or + exists(string other | + isRelevantPackage(other) and + typeModel(package, _, other, _, _) + ) +} + +/** + * Holds if `package,type,path` is used in some CSV row. + */ +pragma[nomagic] +predicate isRelevantFullPath(string package, string type, string path) { + isRelevantPackage(package) and + ( + sourceModel(package, type, path, _) or + sinkModel(package, type, path, _) or + summaryModel(package, type, path, _, _, _) or + typeModel(_, _, package, type, path) + ) +} + +/** A string from a CSV row that should be parsed as an access path. */ +private class AccessPathRange extends AccessPath::Range { + AccessPathRange() { + isRelevantFullPath(_, _, this) + or + exists(string package | isRelevantPackage(package) | + summaryModel(package, _, _, this, _, _) or + summaryModel(package, _, _, _, this, _) + ) + } +} + +/** + * Gets a successor of `node` in the API graph. + */ +bindingset[token] +API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) { + // API graphs use the same label for arguments and parameters. An edge originating from a + // use-node represents an argument, and an edge originating from a def-node represents a parameter. + // We just map both to the same thing. + token.getName() = ["Argument", "Parameter"] and + result = node.getParameter(AccessPath::parseIntUnbounded(token.getAnArgument())) + or + token.getName() = "ReturnValue" and + result = node.getReturn() + or + // Language-specific tokens + result = Specific::getExtraSuccessorFromNode(node, token) +} + +/** + * Gets an API-graph successor for the given invocation. + */ +bindingset[token] +API::Node getSuccessorFromInvoke(Specific::InvokeNode invoke, AccessPathToken token) { + token.getName() = "Argument" and + result = + invoke + .getParameter(AccessPath::parseIntWithArity(token.getAnArgument(), invoke.getNumArgument())) + or + token.getName() = "ReturnValue" and + result = invoke.getReturn() + or + // Language-specific tokens + result = Specific::getExtraSuccessorFromInvoke(invoke, token) +} + +/** + * Holds if `invoke` invokes a call-site filter given by `token`. + */ +pragma[inline] +private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, AccessPathToken token) { + token.getName() = "WithArity" and + invoke.getNumArgument() = AccessPath::parseIntUnbounded(token.getAnArgument()) + or + Specific::invocationMatchesExtraCallSiteFilter(invoke, token) +} + +/** + * Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple. + */ +pragma[nomagic] +private API::Node getNodeFromPath(string package, string type, AccessPath path, int n) { + isRelevantFullPath(package, type, path) and + ( + n = 0 and + exists(string package2, string type2, AccessPath path2 | + typeModel(package, type, package2, type2, path2) and + result = getNodeFromPath(package2, type2, path2, path2.getNumToken()) + ) + or + // Language-specific cases, such as handling of global variables + result = Specific::getExtraNodeFromPath(package, type, path, n) + ) + or + result = getSuccessorFromNode(getNodeFromPath(package, type, path, n - 1), path.getToken(n - 1)) + or + // Similar to the other recursive case, but where the path may have stepped through one or more call-site filters + result = + getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1)) +} + +/** Gets the node identified by the given `(package, type, path)` tuple. */ +API::Node getNodeFromPath(string package, string type, AccessPath path) { + result = getNodeFromPath(package, type, path, path.getNumToken()) +} + +/** + * Gets an invocation identified by the given `(package, type, path)` tuple. + * + * Unlike `getNodeFromPath`, the `path` may end with one or more call-site filters. + */ +Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPath path, int n) { + result = Specific::getAnInvocationOf(getNodeFromPath(package, type, path, n)) + or + result = getInvocationFromPath(package, type, path, n - 1) and + invocationMatchesCallSiteFilter(result, path.getToken(n - 1)) +} + +/** Gets an invocation identified by the given `(package, type, path)` tuple. */ +Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPath path) { + result = getInvocationFromPath(package, type, path, path.getNumToken()) +} + +/** + * Holds if `name` is a valid name for an access path token in the identifying access path. + */ +bindingset[name] +predicate isValidTokenNameInIdentifyingAccessPath(string name) { + name = ["Argument", "Parameter", "ReturnValue", "WithArity"] + or + Specific::isExtraValidTokenNameInIdentifyingAccessPath(name) +} + +/** + * Holds if `name` is a valid name for an access path token with no arguments, occurring + * in an identifying access path. + */ +bindingset[name] +predicate isValidNoArgumentTokenInIdentifyingAccessPath(string name) { + name = "ReturnValue" + or + Specific::isExtraValidNoArgumentTokenInIdentifyingAccessPath(name) +} + +/** + * Holds if `argument` is a valid argument to an access path token with the given `name`, occurring + * in an identifying access path. + */ +bindingset[name, argument] +predicate isValidTokenArgumentInIdentifyingAccessPath(string name, string argument) { + name = ["Argument", "Parameter"] and + argument.regexpMatch("(N-|-)?\\d+(\\.\\.((N-|-)?\\d+)?)?") + or + name = "WithArity" and + argument.regexpMatch("\\d+(\\.\\.(\\d+)?)?") + or + Specific::isExtraValidTokenArgumentInIdentifyingAccessPath(name, argument) +} + +/** + * Module providing access to the imported models in terms of API graph nodes. + */ +module ModelOutput { + /** + * Holds if a CSV source model contributed `source` with the given `kind`. + */ + API::Node getASourceNode(string kind) { + exists(string package, string type, string path | + sourceModel(package, type, path, kind) and + result = getNodeFromPath(package, type, path) + ) + } + + /** + * Holds if a CSV sink model contributed `sink` with the given `kind`. + */ + API::Node getASinkNode(string kind) { + exists(string package, string type, string path | + sinkModel(package, type, path, kind) and + result = getNodeFromPath(package, type, path) + ) + } + + /** + * Holds if a relevant CSV summary exists for these parameters. + */ + predicate relevantSummaryModel( + string package, string type, string path, string input, string output, string kind + ) { + isRelevantPackage(package) and + summaryModel(package, type, path, input, output, kind) + } + + /** + * Holds if a `baseNode` is an invocation identified by the `package,type,path` part of a summary row. + */ + predicate resolvedSummaryBase( + string package, string type, string path, Specific::InvokeNode baseNode + ) { + summaryModel(package, type, path, _, _, _) and + baseNode = getInvocationFromPath(package, type, path) + } + + /** + * Holds if `node` is seen as an instance of `(package,type)` due to a type definition + * contributed by a CSV model. + */ + API::Node getATypeNode(string package, string type) { + exists(string package2, string type2, AccessPath path | + typeModel(package, type, package2, type2, path) and + result = getNodeFromPath(package2, type2, path) + ) + } + + /** + * Gets an error message relating to an invalid CSV row in a model. + */ + string getAWarning() { + // Check number of columns + exists(string row, string kind, int expectedArity, int actualArity | + any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 4 + or + any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 4 + or + any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 6 + or + any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 5 + | + actualArity = count(row.indexOf(";")) + 1 and + actualArity != expectedArity and + result = + "CSV " + kind + " row should have " + expectedArity + " columns but has " + actualArity + + ": " + row + ) + or + // Check names and arguments of access path tokens + exists(AccessPath path, AccessPathToken token | + isRelevantFullPath(_, _, path) and + token = path.getToken(_) + | + not isValidTokenNameInIdentifyingAccessPath(token.getName()) and + result = "Invalid token name '" + token.getName() + "' in access path: " + path + or + isValidTokenNameInIdentifyingAccessPath(token.getName()) and + exists(string argument | + argument = token.getAnArgument() and + not isValidTokenArgumentInIdentifyingAccessPath(token.getName(), argument) and + result = + "Invalid argument '" + argument + "' in token '" + token + "' in access path: " + path + ) + or + isValidTokenNameInIdentifyingAccessPath(token.getName()) and + token.getNumArgument() = 0 and + not isValidNoArgumentTokenInIdentifyingAccessPath(token.getName()) and + result = "Invalid token '" + token + "' is missing its arguments, in access path: " + path + ) + } +} diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll new file mode 100644 index 00000000000..92f7dcbd50b --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -0,0 +1,202 @@ +/** + * Contains the language-specific part of the models-as-data implementation found in `ApiGraphModels.qll`. + * + * It must export the following members: + * ```ql + * class Unit // a unit type + * module AccessPathSyntax // a re-export of the AccessPathSyntax module + * class InvokeNode // a type representing an invocation connected to the API graph + * module API // the API graph module + * predicate isPackageUsed(string package) + * API::Node getExtraNodeFromPath(string package, string type, string path, int n) + * API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) + * API::Node getExtraSuccessorFromInvoke(API::InvokeNode node, AccessPathToken token) + * predicate invocationMatchesExtraCallSiteFilter(API::InvokeNode invoke, AccessPathToken token) + * InvokeNode getAnInvocationOf(API::Node node) + * predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) + * predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) + * predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument) + * ``` + */ + +private import python as PY +private import semmle.python.dataflow.new.DataFlow +private import ApiGraphModels +import semmle.python.ApiGraphs::API as API + +class Unit = PY::Unit; + +// Re-export libraries needed by ApiGraphModels.qll +import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax +private import AccessPathSyntax + +/** + * Holds if models describing `package` may be relevant for the analysis of this database. + */ +predicate isPackageUsed(string package) { exists(API::moduleImport(package)) } + +/** Gets a Python-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */ +bindingset[package, type, path] +API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) { + type = "" and + n = 0 and + result = API::moduleImport(package) and + exists(path) +} + +/** + * Gets a Python-specific API graph successor of `node` reachable by resolving `token`. + */ +bindingset[token] +API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) { + token.getName() = "Member" and + result = node.getMember(token.getAnArgument()) + or + token.getName() = "Instance" and + result = node.getReturn() // In Python `Instance` is just an alias for `ReturnValue` + or + token.getName() = "Awaited" and + result = node.getAwaited() + or + token.getName() = "Subclass" and + result = node.getASubclass*() + or + token.getName() = "Method" and + result = node.getMember(token.getAnArgument()).getReturn() + or + token.getName() = ["Argument", "Parameter"] and + ( + token.getAnArgument() = "self" and + result = node.getSelfParameter() + or + exists(string name | token.getAnArgument() = name + ":" | + result = node.getKeywordParameter(name) + ) + or + token.getAnArgument() = "any" and + result = [node.getParameter(_), node.getKeywordParameter(_)] + or + token.getAnArgument() = "any-named" and + result = node.getKeywordParameter(_) + ) + // Some features don't have MaD tokens yet, they would need to be added to API-graphs first. + // - decorators ("DecoratedClass", "DecoratedMember", "DecoratedParameter") + // - Array/Map elements ("ArrayElement", "Element", "MapKey", "MapValue") +} + +/** + * Gets a Python-specific API graph successor of `node` reachable by resolving `token`. + */ +bindingset[token] +API::Node getExtraSuccessorFromInvoke(API::CallNode node, AccessPathToken token) { + token.getName() = "Instance" and + result = node.getReturn() + or + token.getName() = ["Argument", "Parameter"] and + ( + token.getAnArgument() = "self" and + result = node.getSelfParameter() + or + token.getAnArgument() = "any" and + result = [node.getParameter(_), node.getKeywordParameter(_)] + or + token.getAnArgument() = "any-named" and + result = node.getKeywordParameter(_) + or + exists(string arg | arg + ":" = token.getAnArgument() | result = node.getKeywordParameter(arg)) + ) +} + +/** + * Holds if `invoke` matches the PY-specific call site filter in `token`. + */ +bindingset[token] +predicate invocationMatchesExtraCallSiteFilter(API::CallNode invoke, AccessPathToken token) { + token.getName() = "Call" and exists(invoke) // there is only one kind of call in Python. +} + +/** + * Holds if `path` is an input or output spec for a summary with the given `base` node. + */ +pragma[nomagic] +private predicate relevantInputOutputPath(API::CallNode base, AccessPath inputOrOutput) { + exists(string package, string type, string input, string output, string path | + ModelOutput::relevantSummaryModel(package, type, path, input, output, _) and + ModelOutput::resolvedSummaryBase(package, type, path, base) and + inputOrOutput = [input, output] + ) +} + +/** + * Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`. + */ +private API::Node getNodeFromInputOutputPath(API::CallNode baseNode, AccessPath path, int n) { + relevantInputOutputPath(baseNode, path) and + ( + n = 1 and + result = getSuccessorFromInvoke(baseNode, path.getToken(0)) + or + result = + getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1), path.getToken(n - 1)) + ) +} + +/** + * Gets the API node for the given input/output path, evaluated relative to `baseNode`. + */ +private API::Node getNodeFromInputOutputPath(API::CallNode baseNode, AccessPath path) { + result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken()) +} + +/** + * Holds if a CSV summary contributed the step `pred -> succ` of the given `kind`. + */ +predicate summaryStep(API::Node pred, API::Node succ, string kind) { + exists( + string package, string type, string path, API::CallNode base, AccessPath input, + AccessPath output + | + ModelOutput::relevantSummaryModel(package, type, path, input, output, kind) and + ModelOutput::resolvedSummaryBase(package, type, path, base) and + pred = getNodeFromInputOutputPath(base, input) and + succ = getNodeFromInputOutputPath(base, output) + ) +} + +class InvokeNode = API::CallNode; + +/** Gets an `InvokeNode` corresponding to an invocation of `node`. */ +InvokeNode getAnInvocationOf(API::Node node) { result = node.getACall() } + +/** + * Holds if `name` is a valid name for an access path token in the identifying access path. + */ +bindingset[name] +predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) { + name = ["Member", "Instance", "Awaited", "Call", "Method", "Subclass"] +} + +/** + * Holds if `name` is a valid name for an access path token with no arguments, occurring + * in an identifying access path. + */ +predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) { + name = ["Instance", "Awaited", "Call", "Subclass"] +} + +/** + * Holds if `argument` is a valid argument to an access path token with the given `name`, occurring + * in an identifying access path. + */ +bindingset[name, argument] +predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument) { + name = ["Member", "Method"] and + exists(argument) + or + name = ["Argument", "Parameter"] and + ( + argument = ["self", "any", "any-named"] + or + argument.regexpMatch("\\w+:") // keyword argument + ) +} diff --git a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll index 410eee50b29..5a033664823 100644 --- a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll @@ -59,6 +59,12 @@ module PathInjection { FileSystemAccessAsSink() { this = any(FileSystemAccess e).getAPathArgument() } } + private import semmle.python.frameworks.data.ModelsAsData + + private class DataAsFileSink extends Sink { + DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").getARhs() } + } + /** * A comparison with a constant string, considered as a sanitizer-guard. */ diff --git a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll index 756a1f6b773..cf21a5c0e94 100644 --- a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll @@ -60,4 +60,11 @@ module SqlInjection { * A comparison with a constant string, considered as a sanitizer-guard. */ class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { } + + private import semmle.python.frameworks.data.ModelsAsData + + /** A sink for sql-injection from model data. */ + private class DataAsSqlSink extends Sink { + DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").getARhs() } + } } diff --git a/python/ql/src/experimental/Security/CWE-1236/CsvInjection.py b/python/ql/src/experimental/Security/CWE-1236/CsvInjection.py new file mode 100644 index 00000000000..4c436592bc9 --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-1236/CsvInjection.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Desc :csv injection +""" +import copy +import csv +from flask import Flask +from flask import request +from typing import List + +app = Flask(__name__) + +@app.route('/bad1') +def bad1(): + csv_data = request.args.get('csv') + csvWriter = csv.writer(open("test.csv", "wt")) + csvWriter.writerow(csv_data) + return "bad1" + +@app.route('/good1') +def good1(): + csv_data = request.args.get('csv') + csvWriter = csv.writer(open("test.csv", "wt")) + csvWriter.writerow(santize_for_csv(csv_data)) + return "good1" + +def santize_for_csv(data: str| List[str] | List[List[str]]): + def sanitize(item): + return "'" + item + + unsafe_prefixes = ("+", "=", "-", "@") + if isinstance(data, str): + if data.startswith(unsafe_prefixes): + return sanitize(data) + return data + elif isinstance(data, list) and isinstance(data[0], str): + sanitized_data = copy.deepcopy(data) + for index, item in enumerate(data): + if item.startswith(unsafe_prefixes): + sanitized_data[index] = sanitize(item) + return sanitized_data + elif isinstance(data[0], list) and isinstance(data[0][0], str): + sanitized_data = copy.deepcopy(data) + for outer_index, sublist in enumerate(data): + for inner_index, item in enumerate(sublist): + if item.startswith(unsafe_prefixes): + sanitized_data[outer_index][inner_index] = sanitize(item) + return sanitized_data + else: + raise ValueError("Unsupported data type: " + str(type(data))) + + +if __name__ == '__main__': + app.debug = True + app.run() \ No newline at end of file diff --git a/python/ql/src/experimental/Security/CWE-1236/CsvInjection.qhelp b/python/ql/src/experimental/Security/CWE-1236/CsvInjection.qhelp new file mode 100644 index 00000000000..785e8bdbfef --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-1236/CsvInjection.qhelp @@ -0,0 +1,29 @@ + + + +

CSV Injection, also known as Formula Injection, occurs when websites embed untrusted input inside CSV files.

+

When a CSV format file is opened with a spreadsheet program such as Microsoft Excel or LibreOffice Calc. +this software interprets entries beginning with = as formulas, which may attempt information exfiltration +or other malicious activity when automatically executed by the spreadsheet software.

+ + + +

When generating CSV output, ensure that formula-sensitive metacharacters are effectively escaped or removed from all data before storage in the resultant CSV. +Risky characters include =(equal), +(plus), -(minus), and @(at).

+ +
+ + +

The following examples show the bad case and the good case respectively. +In bad1 method, the data provided by the user is directly stored in the CSV file, which may be attacked. +But in the good1 method, the program will check the data provided by the user, and process the data starting with =(equal), +(plus), -(minus), and @(at) characters safely.

+ + + +
+ +
  • OWASP: CSV Injection.
  • +
    + diff --git a/python/ql/src/experimental/Security/CWE-1236/CsvInjection.ql b/python/ql/src/experimental/Security/CWE-1236/CsvInjection.ql new file mode 100644 index 00000000000..a570461add1 --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-1236/CsvInjection.ql @@ -0,0 +1,20 @@ +/** + * @name Csv Injection + * @description From user-controlled data saved in CSV files, it is easy to attempt information disclosure + * or other malicious activities when automated by spreadsheet software + * @kind path-problem + * @problem.severity error + * @id py/csv-injection + * @tags security + * external/cwe/cwe-1236 + */ + +import python +import DataFlow::PathGraph +import semmle.python.dataflow.new.DataFlow +import experimental.semmle.python.security.injection.CsvInjection + +from CsvInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Csv injection might include code from $@.", source.getNode(), + "this user input" diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll index 8eef245217e..305ba22ce79 100644 --- a/python/ql/src/experimental/semmle/python/Concepts.qll +++ b/python/ql/src/experimental/semmle/python/Concepts.qll @@ -371,6 +371,39 @@ class HeaderDeclaration extends DataFlow::Node { DataFlow::Node getValueArg() { result = range.getValueArg() } } +/** Provides classes for modeling Csv writer APIs. */ +module CsvWriter { + /** + * A data flow node for csv writer. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `CsvWriter` instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Get the parameter value of the csv writer function. + */ + abstract DataFlow::Node getAnInput(); + } +} + +/** + * A data flow node for csv writer. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `CsvWriter::Range` instead. + */ +class CsvWriter extends DataFlow::Node { + CsvWriter::Range range; + + CsvWriter() { this = range } + + /** + * Get the parameter value of the csv writer function. + */ + DataFlow::Node getAnInput() { result = range.getAnInput() } +} + /** * A data-flow node that sets a cookie in an HTTP response. * diff --git a/python/ql/src/experimental/semmle/python/Frameworks.qll b/python/ql/src/experimental/semmle/python/Frameworks.qll index f0b120ceacc..359dc91162b 100644 --- a/python/ql/src/experimental/semmle/python/Frameworks.qll +++ b/python/ql/src/experimental/semmle/python/Frameworks.qll @@ -9,6 +9,7 @@ private import experimental.semmle.python.frameworks.Werkzeug private import experimental.semmle.python.frameworks.LDAP private import experimental.semmle.python.frameworks.NoSQL private import experimental.semmle.python.frameworks.JWT +private import experimental.semmle.python.frameworks.Csv private import experimental.semmle.python.libraries.PyJWT private import experimental.semmle.python.libraries.Python_JWT private import experimental.semmle.python.libraries.Authlib diff --git a/python/ql/src/experimental/semmle/python/frameworks/Csv.qll b/python/ql/src/experimental/semmle/python/frameworks/Csv.qll new file mode 100644 index 00000000000..7f92f85a8ed --- /dev/null +++ b/python/ql/src/experimental/semmle/python/frameworks/Csv.qll @@ -0,0 +1,73 @@ +/** + * Provides classes modeling security-relevant aspects of the `csv` PyPI package. + * See https://docs.python.org/3/library/csv.html + */ + +private import python +private import semmle.python.ApiGraphs +private import experimental.semmle.python.Concepts + +/** + * Provides models for the `csv` PyPI package. + * + * See + * - https://docs.python.org/3/library/csv.html + */ +private module Csv { + private module Writer { + abstract class InstanceSource extends DataFlow::LocalSourceNode { } + + /** A direct instantiation of `csv.writer` or `csv.DictWriter`. */ + private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { + ClassInstantiation() { + this = API::moduleImport("csv").getMember(["writer", "DictWriter"]).getACall() + } + } + + /** Gets a reference to an `csv.writer` or `csv.DictWriter` instance. */ + private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) { + t.start() and + result instanceof InstanceSource + or + exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) + } + + /** Gets a reference to an `csv.writer` or `csv.DictWriter` instance. */ + DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + + /** + * See: + * - https://docs.python.org/3/library/csv.html#csvwriter.writerow + * - https://docs.python.org/3/library/csv.html#csvwriter.writerows + */ + private class CsvWriteCall extends CsvWriter::Range, DataFlow::CallCfgNode { + string methodName; + + CsvWriteCall() { + methodName in ["writerow", "writerows"] and + this.(DataFlow::MethodCallNode).calls(Writer::instance(), methodName) + } + + override DataFlow::Node getAnInput() { + result = this.getArg(0) + or + methodName = "writerow" and + result = this.getArgByName("row") + or + methodName = "writerows" and + result = this.getArgByName("rows") + } + } + + /** + * See: https://docs.python.org/3/library/csv.html#csv.DictWriter + */ + private class DictWriterInstance extends CsvWriter::Range, DataFlow::CallCfgNode { + DictWriterInstance() { this = API::moduleImport("csv").getMember("DictWriter").getACall() } + + override DataFlow::Node getAnInput() { + result in [this.getArg(1), this.getArgByName("fieldnames")] + } + } + } +} diff --git a/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll b/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll new file mode 100644 index 00000000000..7da7b51a6df --- /dev/null +++ b/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll @@ -0,0 +1,36 @@ +import python +import experimental.semmle.python.Concepts +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking +import semmle.python.dataflow.new.BarrierGuards +import semmle.python.dataflow.new.RemoteFlowSources + +/** + * A taint-tracking configuration for tracking untrusted user input used in file read. + */ +class CsvInjectionFlowConfig extends TaintTracking::Configuration { + CsvInjectionFlowConfig() { this = "CsvInjectionFlowConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink = any(CsvWriter cw).getAnInput() } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof StartsWithCheck or + guard instanceof StringConstCompare + } +} + +private class StartsWithCheck extends DataFlow::BarrierGuard { + DataFlow::MethodCallNode mc; + + StartsWithCheck() { + this = mc.asCfgNode() and + mc.calls(_, "startswith") + } + + override predicate checks(ControlFlowNode node, boolean branch) { + node = mc.getObject().asCfgNode() and + branch = true + } +} diff --git a/python/ql/test/experimental/meta/MaDTest.qll b/python/ql/test/experimental/meta/MaDTest.qll new file mode 100644 index 00000000000..345fc973284 --- /dev/null +++ b/python/ql/test/experimental/meta/MaDTest.qll @@ -0,0 +1,46 @@ +import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.dataflow.new.internal.PrintNode +private import semmle.python.frameworks.data.ModelsAsData +// need to import Frameworks to get the actual modeling imported +private import semmle.python.Frameworks +// this import needs to be public to get the query predicates propagated to the actual test files +import TestUtilities.InlineExpectationsTest + +class MadSinkTest extends InlineExpectationsTest { + MadSinkTest() { this = "MadSinkTest" } + + override string getARelevantTag() { + exists(string kind | exists(ModelOutput::getASinkNode(kind)) | result = "mad-sink__" + kind) + } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(location.getFile().getRelativePath()) and + exists(DataFlow::Node sink, string kind | + sink = ModelOutput::getASinkNode(kind).getARhs() and + location = sink.getLocation() and + element = sink.toString() and + value = prettyNodeForInlineTest(sink) and + tag = "mad-sink__" + kind + ) + } +} + +class MadSourceTest extends InlineExpectationsTest { + MadSourceTest() { this = "MadSourceTest" } + + override string getARelevantTag() { + exists(string kind | exists(ModelOutput::getASourceNode(kind)) | result = "mad-source__" + kind) + } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(location.getFile().getRelativePath()) and + exists(DataFlow::Node source, string kind | + source = ModelOutput::getASourceNode(kind).getAnImmediateUse() and + location = source.getLocation() and + element = source.toString() and + value = prettyNodeForInlineTest(source) and + tag = "mad-source__" + kind + ) + } +} diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1236/CsvInjection.expected b/python/ql/test/experimental/query-tests/Security/CWE-1236/CsvInjection.expected new file mode 100644 index 00000000000..2b6fb18f47b --- /dev/null +++ b/python/ql/test/experimental/query-tests/Security/CWE-1236/CsvInjection.expected @@ -0,0 +1,19 @@ +edges +| csv_bad.py:16:16:16:22 | ControlFlowNode for request | csv_bad.py:16:16:16:27 | ControlFlowNode for Attribute | +| csv_bad.py:16:16:16:27 | ControlFlowNode for Attribute | csv_bad.py:18:24:18:31 | ControlFlowNode for csv_data | +| csv_bad.py:16:16:16:27 | ControlFlowNode for Attribute | csv_bad.py:19:25:19:32 | ControlFlowNode for csv_data | +| csv_bad.py:24:16:24:22 | ControlFlowNode for request | csv_bad.py:24:16:24:27 | ControlFlowNode for Attribute | +| csv_bad.py:24:16:24:27 | ControlFlowNode for Attribute | csv_bad.py:25:46:25:53 | ControlFlowNode for csv_data | +nodes +| csv_bad.py:16:16:16:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| csv_bad.py:16:16:16:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| csv_bad.py:18:24:18:31 | ControlFlowNode for csv_data | semmle.label | ControlFlowNode for csv_data | +| csv_bad.py:19:25:19:32 | ControlFlowNode for csv_data | semmle.label | ControlFlowNode for csv_data | +| csv_bad.py:24:16:24:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| csv_bad.py:24:16:24:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| csv_bad.py:25:46:25:53 | ControlFlowNode for csv_data | semmle.label | ControlFlowNode for csv_data | +subpaths +#select +| csv_bad.py:18:24:18:31 | ControlFlowNode for csv_data | csv_bad.py:16:16:16:22 | ControlFlowNode for request | csv_bad.py:18:24:18:31 | ControlFlowNode for csv_data | Csv injection might include code from $@. | csv_bad.py:16:16:16:22 | ControlFlowNode for request | this user input | +| csv_bad.py:19:25:19:32 | ControlFlowNode for csv_data | csv_bad.py:16:16:16:22 | ControlFlowNode for request | csv_bad.py:19:25:19:32 | ControlFlowNode for csv_data | Csv injection might include code from $@. | csv_bad.py:16:16:16:22 | ControlFlowNode for request | this user input | +| csv_bad.py:25:46:25:53 | ControlFlowNode for csv_data | csv_bad.py:24:16:24:22 | ControlFlowNode for request | csv_bad.py:25:46:25:53 | ControlFlowNode for csv_data | Csv injection might include code from $@. | csv_bad.py:24:16:24:22 | ControlFlowNode for request | this user input | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1236/CsvInjection.qlref b/python/ql/test/experimental/query-tests/Security/CWE-1236/CsvInjection.qlref new file mode 100644 index 00000000000..d9cd7e9ca51 --- /dev/null +++ b/python/ql/test/experimental/query-tests/Security/CWE-1236/CsvInjection.qlref @@ -0,0 +1 @@ +experimental/Security/CWE-1236/CsvInjection.ql \ No newline at end of file diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1236/csv_bad.py b/python/ql/test/experimental/query-tests/Security/CWE-1236/csv_bad.py new file mode 100644 index 00000000000..6e204d1f3c5 --- /dev/null +++ b/python/ql/test/experimental/query-tests/Security/CWE-1236/csv_bad.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +@Desc :csv injection +""" +import copy +import csv +from flask import Flask +from flask import request +from typing import List + +app = Flask(__name__) + +@app.route('/bad1') +def bad1(): + csv_data = request.args.get('csv') + csvWriter = csv.writer(open("test.csv", "wt")) + csvWriter.writerow(csv_data) # bad + csvWriter.writerows(csv_data) # bad + return "bad1" + +@app.route('/bad2') +def bad2(): + csv_data = request.args.get('csv') + csvWriter = csv.DictWriter(f, fieldnames=csv_data) # bad + csvWriter.writeheader() + return "bad2" + +if __name__ == '__main__': + app.debug = True + app.run() \ No newline at end of file diff --git a/python/ql/test/library-tests/filters/tests/Filter.expected b/python/ql/test/library-tests/filters/tests/Filter.expected index a4f643aac45..9aeda5bd3f4 100644 --- a/python/ql/test/library-tests/filters/tests/Filter.expected +++ b/python/ql/test/library-tests/filters/tests/Filter.expected @@ -1,6 +1,13 @@ -| test.py:4:1:4:23 | Class MyTest | -| test.py:6:5:6:21 | Function test_1 | -| test.py:9:5:9:21 | Function test_2 | | test_foo.py:3:1:3:15 | Function test_foo | | unittest_test.py:3:1:3:33 | Class FooTest | | unittest_test.py:4:5:4:25 | Function test_valid | +| unittest_test.py:7:1:7:49 | Class FunctionFooTest | +| unittest_test.py:8:5:8:25 | Function test_valid | +| unittest_test.py:11:1:11:50 | Class AsyncTest | +| unittest_test.py:12:11:12:31 | Function test_valid | +| unittest_test.py:17:1:17:45 | Class MyDjangoUnitTest | +| unittest_test.py:18:5:18:25 | Function test_valid | +| unittest_test.py:23:1:23:56 | Class MyFlaskUnitTest | +| unittest_test.py:24:5:24:25 | Function test_valid | +| unittest_test.py:29:1:29:59 | Class MyTornadoUnitTest | +| unittest_test.py:30:5:30:25 | Function test_valid | diff --git a/python/ql/test/library-tests/filters/tests/test.py b/python/ql/test/library-tests/filters/tests/test.py deleted file mode 100644 index 261d2fc9f36..00000000000 --- a/python/ql/test/library-tests/filters/tests/test.py +++ /dev/null @@ -1,10 +0,0 @@ -class TestCase: - pass - -class MyTest(TestCase): - - def test_1(self): - pass - - def test_2(self): - pass diff --git a/python/ql/test/library-tests/filters/tests/unittest_test.py b/python/ql/test/library-tests/filters/tests/unittest_test.py index 6d28a61438a..d8425f0f1fa 100644 --- a/python/ql/test/library-tests/filters/tests/unittest_test.py +++ b/python/ql/test/library-tests/filters/tests/unittest_test.py @@ -3,3 +3,29 @@ import unittest class FooTest(unittest.TestCase): def test_valid(self): pass + +class FunctionFooTest(unittest.FunctionTestCase): + def test_valid(self): + pass + +class AsyncTest(unittest.IsolatedAsyncioTestCase): + async def test_valid(self): + pass + +# django -- see https://docs.djangoproject.com/en/4.0/topics/testing/overview/ +import django.test +class MyDjangoUnitTest(django.test.TestCase): + def test_valid(self): + pass + +# flask -- see https://pythonhosted.org/Flask-Testing/ +import flask_testing +class MyFlaskUnitTest(flask_testing.LiveServerTestCase): + def test_valid(self): + pass + +# tornado -- see https://www.tornadoweb.org/en/stable/testing.html#tornado.testing.AsyncHTTPTestCase +import tornado.testing +class MyTornadoUnitTest(tornado.testing.AsyncHTTPTestCase): + def test_valid(self): + pass diff --git a/python/ql/test/library-tests/frameworks/asyncpg/MaDTest.expected b/python/ql/test/library-tests/frameworks/asyncpg/MaDTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/frameworks/asyncpg/MaDTest.ql b/python/ql/test/library-tests/frameworks/asyncpg/MaDTest.ql new file mode 100644 index 00000000000..fef4356ab35 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/asyncpg/MaDTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.MaDTest diff --git a/python/ql/test/library-tests/frameworks/asyncpg/test.py b/python/ql/test/library-tests/frameworks/asyncpg/test.py index e4b3c895ece..e2e5e1c5826 100644 --- a/python/ql/test/library-tests/frameworks/asyncpg/test.py +++ b/python/ql/test/library-tests/frameworks/asyncpg/test.py @@ -7,17 +7,17 @@ async def test_connection(): try: # The file-like object is passed in as a keyword-only argument. # See https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection.copy_from_query - await conn.copy_from_query("sql", output="filepath") # $ getSql="sql" getAPathArgument="filepath" - await conn.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ getSql="sql" getAPathArgument="filepath" + await conn.copy_from_query("sql", output="filepath") # $ mad-sink__sql-injection="sql" mad-sink__path-injection="filepath" + await conn.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ mad-sink__sql-injection="sql" mad-sink__path-injection="filepath" - await conn.copy_from_table("table", output="filepath") # $ getAPathArgument="filepath" - await conn.copy_to_table("table", source="filepath") # $ getAPathArgument="filepath" + await conn.copy_from_table("table", output="filepath") # $ mad-sink__path-injection="filepath" + await conn.copy_to_table("table", source="filepath") # $ mad-sink__path-injection="filepath" - await conn.execute("sql") # $ getSql="sql" - await conn.executemany("sql") # $ getSql="sql" - await conn.fetch("sql") # $ getSql="sql" - await conn.fetchrow("sql") # $ getSql="sql" - await conn.fetchval("sql") # $ getSql="sql" + await conn.execute("sql") # $ mad-sink__sql-injection="sql" + await conn.executemany("sql") # $ mad-sink__sql-injection="sql" + await conn.fetch("sql") # $ mad-sink__sql-injection="sql" + await conn.fetchrow("sql") # $ mad-sink__sql-injection="sql" + await conn.fetchval("sql") # $ mad-sink__sql-injection="sql" finally: await conn.close() @@ -27,11 +27,11 @@ async def test_prepared_statement(): conn = await asyncpg.connect() try: - pstmt = await conn.prepare("psql") # $ constructedSql="psql" - pstmt.executemany() # $ getSql="psql" - pstmt.fetch() # $ getSql="psql" - pstmt.fetchrow() # $ getSql="psql" - pstmt.fetchval() # $ getSql="psql" + pstmt = await conn.prepare("psql") # $ mad-sink__sql-injection="psql" + pstmt.executemany() + pstmt.fetch() + pstmt.fetchrow() + pstmt.fetchval() finally: await conn.close() @@ -46,7 +46,7 @@ async def test_cursor(): cursor = await conn.cursor("sql") # $ getSql="sql" constructedSql="sql" await cursor.fetch() - pstmt = await conn.prepare("psql") # $ constructedSql="psql" + pstmt = await conn.prepare("psql") # $ mad-sink__sql-injection="psql" pcursor = await pstmt.cursor() # $ getSql="psql" await pcursor.fetch() @@ -69,23 +69,23 @@ async def test_connection_pool(): pool = await asyncpg.create_pool() try: - await pool.copy_from_query("sql", output="filepath") # $ getSql="sql" getAPathArgument="filepath" - await pool.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ getSql="sql" getAPathArgument="filepath" - await pool.copy_from_table("table", output="filepath") # $ getAPathArgument="filepath" - await pool.copy_to_table("table", source="filepath") # $ getAPathArgument="filepath" + await pool.copy_from_query("sql", output="filepath") # $ mad-sink__sql-injection="sql" mad-sink__path-injection="filepath" + await pool.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ mad-sink__sql-injection="sql" mad-sink__path-injection="filepath" + await pool.copy_from_table("table", output="filepath") # $ mad-sink__path-injection="filepath" + await pool.copy_to_table("table", source="filepath") # $ mad-sink__path-injection="filepath" - await pool.execute("sql") # $ getSql="sql" - await pool.executemany("sql") # $ getSql="sql" - await pool.fetch("sql") # $ getSql="sql" - await pool.fetchrow("sql") # $ getSql="sql" - await pool.fetchval("sql") # $ getSql="sql" + await pool.execute("sql") # $ mad-sink__sql-injection="sql" + await pool.executemany("sql") # $ mad-sink__sql-injection="sql" + await pool.fetch("sql") # $ mad-sink__sql-injection="sql" + await pool.fetchrow("sql") # $ mad-sink__sql-injection="sql" + await pool.fetchval("sql") # $ mad-sink__sql-injection="sql" async with pool.acquire() as conn: - await conn.execute("sql") # $ getSql="sql" + await conn.execute("sql") # $ mad-sink__sql-injection="sql" conn = await pool.acquire() try: - await conn.fetch("sql") # $ getSql="sql" + await conn.fetch("sql") # $ mad-sink__sql-injection="sql" finally: await pool.release(conn) @@ -93,13 +93,13 @@ async def test_connection_pool(): await pool.close() async with asyncpg.create_pool() as pool: - await pool.execute("sql") # $ getSql="sql" + await pool.execute("sql") # $ mad-sink__sql-injection="sql" async with pool.acquire() as conn: - await conn.execute("sql") # $ getSql="sql" + await conn.execute("sql") # $ mad-sink__sql-injection="sql" conn = await pool.acquire() try: - await conn.fetch("sql") # $ getSql="sql" + await conn.fetch("sql") # $ mad-sink__sql-injection="sql" finally: await pool.release(conn) diff --git a/python/ql/test/library-tests/frameworks/data/test.expected b/python/ql/test/library-tests/frameworks/data/test.expected new file mode 100644 index 00000000000..68de6ecd878 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/data/test.expected @@ -0,0 +1,103 @@ +taintFlow +| test.py:3:5:3:15 | ControlFlowNode for getSource() | test.py:4:8:4:8 | ControlFlowNode for x | +| test.py:3:5:3:15 | ControlFlowNode for getSource() | test.py:7:17:7:17 | ControlFlowNode for x | +| test.py:9:8:9:14 | ControlFlowNode for alias() | test.py:9:8:9:14 | ControlFlowNode for alias() | +| test.py:10:8:10:22 | ControlFlowNode for Attribute() | test.py:10:8:10:22 | ControlFlowNode for Attribute() | +| test.py:11:8:11:30 | ControlFlowNode for Attribute() | test.py:11:8:11:30 | ControlFlowNode for Attribute() | +| test.py:71:28:71:38 | ControlFlowNode for getSource() | test.py:71:8:71:39 | ControlFlowNode for Attribute() | +| test.py:75:5:75:15 | ControlFlowNode for getSource() | test.py:76:22:76:22 | ControlFlowNode for x | +| test.py:75:5:75:15 | ControlFlowNode for getSource() | test.py:77:22:77:22 | ControlFlowNode for y | +| test.py:81:36:81:46 | ControlFlowNode for getSource() | test.py:81:8:81:47 | ControlFlowNode for Attribute() | +| test.py:83:50:83:60 | ControlFlowNode for getSource() | test.py:83:8:83:61 | ControlFlowNode for Attribute() | +| test.py:86:49:86:59 | ControlFlowNode for getSource() | test.py:86:8:86:60 | ControlFlowNode for Attribute() | +| test.py:87:56:87:66 | ControlFlowNode for getSource() | test.py:87:8:87:67 | ControlFlowNode for Attribute() | +isSink +| test.py:4:8:4:8 | ControlFlowNode for x | test-sink | +| test.py:7:17:7:17 | ControlFlowNode for x | test-sink | +| test.py:9:8:9:14 | ControlFlowNode for alias() | test-sink | +| test.py:10:8:10:22 | ControlFlowNode for Attribute() | test-sink | +| test.py:11:8:11:30 | ControlFlowNode for Attribute() | test-sink | +| test.py:12:8:12:34 | ControlFlowNode for Attribute() | test-sink | +| test.py:16:11:16:13 | ControlFlowNode for one | test-sink | +| test.py:17:19:17:21 | ControlFlowNode for two | test-sink | +| test.py:17:24:17:28 | ControlFlowNode for three | test-sink | +| test.py:17:31:17:34 | ControlFlowNode for four | test-sink | +| test.py:18:37:18:40 | ControlFlowNode for five | test-sink | +| test.py:19:21:19:26 | ControlFlowNode for second | test-sink | +| test.py:30:21:30:23 | ControlFlowNode for one | test-sink | +| test.py:32:22:32:24 | ControlFlowNode for one | test-sink | +| test.py:32:27:32:29 | ControlFlowNode for two | test-sink | +| test.py:33:22:33:24 | ControlFlowNode for one | test-sink | +| test.py:33:27:33:29 | ControlFlowNode for two | test-sink | +| test.py:33:32:33:36 | ControlFlowNode for three | test-sink | +| test.py:57:27:57:33 | ControlFlowNode for arg_pos | test-sink | +| test.py:66:17:66:20 | ControlFlowNode for arg1 | test-sink | +| test.py:66:23:66:26 | ControlFlowNode for arg2 | test-sink | +| test.py:66:34:66:43 | ControlFlowNode for namedThing | test-sink | +| test.py:67:34:67:44 | ControlFlowNode for secondNamed | test-sink | +| test.py:71:8:71:39 | ControlFlowNode for Attribute() | test-sink | +| test.py:72:8:72:47 | ControlFlowNode for Attribute() | test-sink | +| test.py:76:22:76:22 | ControlFlowNode for x | test-sink | +| test.py:77:22:77:22 | ControlFlowNode for y | test-sink | +| test.py:78:22:78:22 | ControlFlowNode for z | test-sink | +| test.py:81:8:81:47 | ControlFlowNode for Attribute() | test-sink | +| test.py:82:8:82:54 | ControlFlowNode for Attribute() | test-sink | +| test.py:83:8:83:61 | ControlFlowNode for Attribute() | test-sink | +| test.py:85:8:85:53 | ControlFlowNode for Attribute() | test-sink | +| test.py:86:8:86:60 | ControlFlowNode for Attribute() | test-sink | +| test.py:87:8:87:67 | ControlFlowNode for Attribute() | test-sink | +| test.py:89:21:89:23 | ControlFlowNode for one | test-sink | +| test.py:91:21:91:23 | ControlFlowNode for one | test-sink | +| test.py:91:30:91:32 | ControlFlowNode for two | test-sink | +| test.py:98:6:98:9 | ControlFlowNode for baz2 | test-sink | +isSource +| test.py:3:5:3:15 | ControlFlowNode for getSource() | test-source | +| test.py:9:8:9:14 | ControlFlowNode for alias() | test-source | +| test.py:10:8:10:14 | ControlFlowNode for alias() | test-source | +| test.py:10:8:10:22 | ControlFlowNode for Attribute() | test-source | +| test.py:11:8:11:14 | ControlFlowNode for alias() | test-source | +| test.py:11:8:11:22 | ControlFlowNode for Attribute() | test-source | +| test.py:11:8:11:30 | ControlFlowNode for Attribute() | test-source | +| test.py:12:8:12:14 | ControlFlowNode for alias() | test-source | +| test.py:12:8:12:22 | ControlFlowNode for Attribute() | test-source | +| test.py:23:24:23:26 | ControlFlowNode for one | test-source | +| test.py:24:33:24:35 | ControlFlowNode for two | test-source | +| test.py:24:38:24:42 | ControlFlowNode for three | test-source | +| test.py:24:45:24:48 | ControlFlowNode for four | test-source | +| test.py:25:34:25:39 | ControlFlowNode for second | test-source | +| test.py:39:11:39:20 | ControlFlowNode for Await | test-source | +| test.py:41:8:41:27 | ControlFlowNode for Attribute() | test-source | +| test.py:46:7:46:16 | ControlFlowNode for SubClass() | test-source | +| test.py:53:7:53:16 | ControlFlowNode for Attribute() | test-source | +| test.py:60:13:60:16 | ControlFlowNode for self | test-source | +| test.py:60:24:60:28 | ControlFlowNode for named | test-source | +| test.py:63:36:63:39 | ControlFlowNode for arg2 | test-source | +| test.py:63:42:63:45 | ControlFlowNode for arg3 | test-source | +| test.py:63:48:63:51 | ControlFlowNode for arg4 | test-source | +| test.py:63:54:63:57 | ControlFlowNode for arg5 | test-source | +| test.py:71:28:71:38 | ControlFlowNode for getSource() | test-source | +| test.py:72:36:72:46 | ControlFlowNode for getSource() | test-source | +| test.py:75:5:75:15 | ControlFlowNode for getSource() | test-source | +| test.py:81:36:81:46 | ControlFlowNode for getSource() | test-source | +| test.py:82:43:82:53 | ControlFlowNode for getSource() | test-source | +| test.py:83:50:83:60 | ControlFlowNode for getSource() | test-source | +| test.py:85:42:85:52 | ControlFlowNode for getSource() | test-source | +| test.py:86:49:86:59 | ControlFlowNode for getSource() | test-source | +| test.py:87:56:87:66 | ControlFlowNode for getSource() | test-source | +| test.py:101:29:101:31 | ControlFlowNode for arg | test-source | +| test.py:104:24:104:29 | ControlFlowNode for param1 | test-source | +| test.py:104:32:104:37 | ControlFlowNode for param2 | test-source | +| test.py:107:24:107:28 | ControlFlowNode for name1 | test-source | +| test.py:107:31:107:35 | ControlFlowNode for name2 | test-source | +syntaxErrors +| Member[foo | +| Member[foo] .Member[bar] | +| Member[foo] Member[bar] | +| Member[foo], Member[bar] | +| Member[foo],Member[bar] | +| Member[foo]. Member[bar] | +| Member[foo]..Member[bar] | +| Member[foo]Member[bar] | +| Member[foo]] | +| Member[foo]].Member[bar] | +warning diff --git a/python/ql/test/library-tests/frameworks/data/test.py b/python/ql/test/library-tests/frameworks/data/test.py new file mode 100644 index 00000000000..ea1a6e0d4d4 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/data/test.py @@ -0,0 +1,108 @@ +from testlib import getSource, mySink, alias + +x = getSource() +mySink(x) + +mySink(foo=x) # OK +mySink(sinkName=x) # NOT OK + +mySink(alias()) # NOT OK +mySink(alias().chain()) # NOT OK +mySink(alias().chain().chain()) # NOT OK +mySink(alias().chain().safeThing()) # OK + +from testlib import Args + +Args.arg0(one, two, three, four, five) +Args.arg1to3(one, two, three, four, five) +Args.lastarg(one, two, three, four, five) +Args.nonFist(first, second) + +from testlib import Callbacks + +Callbacks.first(lambda one, two, three, four, five: 0) +Callbacks.param1to3(lambda one, two, three, four, five: 0) +Callbacks.nonFirst(lambda first, second: 0) + +from testlib import CallFilter + +CallFilter.arityOne(one, two) # NO match +CallFilter.arityOne(one) # Match +CallFilter.twoOrMore(one) # NO match +CallFilter.twoOrMore(one, two) # Match +CallFilter.twoOrMore(one, two, three) # Match + +from testlib import CommonTokens + +async def async_func(): + prom = CommonTokens.makePromise(1); + val = await prom + +inst = CommonTokens.Class() + +class SubClass (CommonTokens.Super): + pass + +sub = SubClass() + +class Sub2Class (CommonTokens.Class): + pass + +sub2 = Sub2Class() # TODO: Currently not recognized as an instance of CommonTokens.Class + +val = inst.foo() + +from testlib import ArgPos + +arg_pos = ArgPos(); val = arg_pos.self_thing(arg, named=2); + +class SubClass (ArgPos.MyClass): + def foo(self, arg, named=2, otherName=3): + pass + + def secondAndAfter(self, arg1, arg2, arg3, arg4, arg5): + pass + +ArgPos.anyParam(arg1, arg2, name=namedThing) +ArgPos.anyNamed(arg4, arg5, name=secondNamed) + +from testlib import Steps + +mySink(Steps.preserveTaint(getSource())) # FLOW +mySink(Steps.preserveTaint("safe", getSource())) # NO FLOW + +Steps.taintIntoCallback( + getSource(), + lambda x: mySink(x), # FLOW + lambda y: mySink(y), # FLOW + lambda z: mySink(z) # NO FLOW +) + +mySink(Steps.preserveArgZeroAndTwo(getSource())) # FLOW +mySink(Steps.preserveArgZeroAndTwo("foo", getSource())) # NO FLOW +mySink(Steps.preserveArgZeroAndTwo("foo", "bar", getSource())) # FLOW + +mySink(Steps.preserveAllButFirstArgument(getSource())) # NO FLOW +mySink(Steps.preserveAllButFirstArgument("foo", getSource())) # FLOW +mySink(Steps.preserveAllButFirstArgument("foo", "bar", getSource())) # FLOW + +CallFilter.arityOne(one) # match +CallFilter.arityOne(one=one) # NO match +CallFilter.arityOne(one, two=two) # match - on both the named and positional arguments +CallFilter.arityOne(one=one, two=two) # NO match + +from foo1.bar import baz1 +baz1(baz1) # no match, and that's the point. + +from foo2.bar import baz2 +baz2(baz2) # match + +class OtherSubClass (ArgPos.MyClass): + def otherSelfTest(self, arg, named=2, otherName=3): # test that Parameter[0] hits `arg`. + pass + + def anyParam(self, param1, param2): # Parameter[any] matches all non-self parameters + pass + + def anyNamed(self, name1, name2=2): # Parameter[any-named] matches all non-self named parameters + pass diff --git a/python/ql/test/library-tests/frameworks/data/test.ql b/python/ql/test/library-tests/frameworks/data/test.ql new file mode 100644 index 00000000000..86f960b1adf --- /dev/null +++ b/python/ql/test/library-tests/frameworks/data/test.ql @@ -0,0 +1,127 @@ +import python +import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax +import semmle.python.frameworks.data.ModelsAsData +import semmle.python.dataflow.new.TaintTracking +import semmle.python.dataflow.new.DataFlow +private import semmle.python.ApiGraphs + +class Steps extends ModelInput::SummaryModelCsv { + override predicate row(string row) { + // package;type;path;input;output;kind + row = + [ + "testlib;;Member[Steps].Member[preserveTaint].Call;Argument[0];ReturnValue;taint", + "testlib;;Member[Steps].Member[taintIntoCallback];Argument[0];Argument[1..2].Parameter[0];taint", + "testlib;;Member[Steps].Member[preserveArgZeroAndTwo];Argument[0,2];ReturnValue;taint", + "testlib;;Member[Steps].Member[preserveAllButFirstArgument].Call;Argument[1..];ReturnValue;taint", + ] + } +} + +class Types extends ModelInput::TypeModelCsv { + override predicate row(string row) { + // package1;type1;package2;type2;path + row = + [ + "testlib;Alias;testlib;;Member[alias].ReturnValue", + "testlib;Alias;testlib;Alias;Member[chain].ReturnValue", + ] + } +} + +class Sinks extends ModelInput::SinkModelCsv { + override predicate row(string row) { + // package;type;path;kind + row = + [ + "testlib;;Member[mySink].Argument[0,sinkName:];test-sink", + // testing argument syntax + "testlib;;Member[Args].Member[arg0].Argument[0];test-sink", // + "testlib;;Member[Args].Member[arg1to3].Argument[1..3];test-sink", // + "testlib;;Member[Args].Member[lastarg].Argument[N-1];test-sink", // + "testlib;;Member[Args].Member[nonFist].Argument[1..];test-sink", // + // callsite filter. + "testlib;;Member[CallFilter].Member[arityOne].WithArity[1].Argument[any];test-sink", // + "testlib;;Member[CallFilter].Member[twoOrMore].WithArity[2..].Argument[0..];test-sink", // + // testing non-positional arguments + "testlib;;Member[ArgPos].Instance.Member[self_thing].Argument[self];test-sink", // + // any argument + "testlib;;Member[ArgPos].Member[anyParam].Argument[any];test-sink", // + "testlib;;Member[ArgPos].Member[anyNamed].Argument[any-named];test-sink", // + // testing package syntax + "foo1.bar;;Member[baz1].Argument[any];test-sink", // + "foo2;;Member[bar].Member[baz2].Argument[any];test-sink", // + ] + } +} + +class Sources extends ModelInput::SourceModelCsv { + // package;type;path;kind + override predicate row(string row) { + row = + [ + "testlib;;Member[getSource].ReturnValue;test-source", // + "testlib;Alias;;test-source", + // testing parameter syntax + "testlib;;Member[Callbacks].Member[first].Argument[0].Parameter[0];test-source", // + "testlib;;Member[Callbacks].Member[param1to3].Argument[0].Parameter[1..3];test-source", // + "testlib;;Member[Callbacks].Member[nonFirst].Argument[0].Parameter[1..];test-source", // + // Common tokens. + "testlib;;Member[CommonTokens].Member[makePromise].ReturnValue.Awaited;test-source", // + "testlib;;Member[CommonTokens].Member[Class].Instance;test-source", // + "testlib;;Member[CommonTokens].Member[Super].Subclass.Instance;test-source", // + // method + "testlib;;Member[CommonTokens].Member[Class].Instance.Method[foo];test-source", // + // testing non-positional arguments + "testlib;;Member[ArgPos].Member[MyClass].Subclass.Member[foo].Parameter[self];test-source", // + "testlib;;Member[ArgPos].Member[MyClass].Subclass.Member[foo].Parameter[named:];test-source", // + "testlib;;Member[ArgPos].Member[MyClass].Subclass.Member[secondAndAfter].Parameter[1..];test-source", // + "testlib;;Member[ArgPos].Member[MyClass].Subclass.Member[otherSelfTest].Parameter[0];test-source", // + "testlib;;Member[ArgPos].Member[MyClass].Subclass.Member[anyParam].Parameter[any];test-source", // + "testlib;;Member[ArgPos].Member[MyClass].Subclass.Member[anyNamed].Parameter[any-named];test-source", // + ] + } +} + +class BasicTaintTracking extends TaintTracking::Configuration { + BasicTaintTracking() { this = "BasicTaintTracking" } + + override predicate isSource(DataFlow::Node source) { + source = ModelOutput::getASourceNode("test-source").getAnImmediateUse() + } + + override predicate isSink(DataFlow::Node sink) { + sink = ModelOutput::getASinkNode("test-sink").getARhs() + } +} + +query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) { + any(BasicTaintTracking tr).hasFlow(source, sink) +} + +query predicate isSink(DataFlow::Node node, string kind) { + node = ModelOutput::getASinkNode(kind).getARhs() +} + +query predicate isSource(DataFlow::Node node, string kind) { + node = ModelOutput::getASourceNode(kind).getAnImmediateUse() +} + +class SyntaxErrorTest extends ModelInput::SinkModelCsv { + override predicate row(string row) { + row = + [ + "testlib;;Member[foo],Member[bar];test-sink", "testlib;;Member[foo] Member[bar];test-sink", + "testlib;;Member[foo]. Member[bar];test-sink", + "testlib;;Member[foo], Member[bar];test-sink", + "testlib;;Member[foo]..Member[bar];test-sink", + "testlib;;Member[foo] .Member[bar];test-sink", "testlib;;Member[foo]Member[bar];test-sink", + "testlib;;Member[foo;test-sink", "testlib;;Member[foo]];test-sink", + "testlib;;Member[foo]].Member[bar];test-sink" + ] + } +} + +query predicate syntaxErrors(AccessPathSyntax::AccessPath path) { path.hasSyntaxError() } + +query predicate warning = ModelOutput::getAWarning/0; diff --git a/python/ql/test/library-tests/frameworks/data/warnings.expected b/python/ql/test/library-tests/frameworks/data/warnings.expected new file mode 100644 index 00000000000..5cebb548358 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/data/warnings.expected @@ -0,0 +1,7 @@ +| CSV type row should have 5 columns but has 2: test;TooFewColumns | +| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns | +| Invalid argument '0-1' in token 'Argument[0-1]' in access path: Method[foo].Argument[0-1] | +| Invalid argument '*' in token 'Argument[*]' in access path: Method[foo].Argument[*] | +| Invalid token 'Argument' is missing its arguments, in access path: Method[foo].Argument | +| Invalid token 'Member' is missing its arguments, in access path: Method[foo].Member | +| Invalid token name 'Arg' in access path: Method[foo].Arg[0] | diff --git a/python/ql/test/library-tests/frameworks/data/warnings.ql b/python/ql/test/library-tests/frameworks/data/warnings.ql new file mode 100644 index 00000000000..3443233179e --- /dev/null +++ b/python/ql/test/library-tests/frameworks/data/warnings.ql @@ -0,0 +1,25 @@ +import python +import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax +import semmle.python.frameworks.data.internal.ApiGraphModels as ApiGraphModels +import semmle.python.frameworks.data.ModelsAsData + +private class InvalidTypeModel extends ModelInput::TypeModelCsv { + override predicate row(string row) { + row = + [ + "test;TooManyColumns;;;Member[Foo].Instance;too;many;columns", // + "test;TooFewColumns", // + "test;X;test;Y;Method[foo].Arg[0]", // + "test;X;test;Y;Method[foo].Argument[0-1]", // + "test;X;test;Y;Method[foo].Argument[*]", // + "test;X;test;Y;Method[foo].Argument", // + "test;X;test;Y;Method[foo].Member", // + ] + } +} + +class IsTesting extends ApiGraphModels::TestAllModels { + IsTesting() { this = this } +} + +query predicate warning = ModelOutput::getAWarning/0; diff --git a/python/ql/test/query-tests/Statements/asserts/assert.py b/python/ql/test/query-tests/Statements/asserts/assert.py index 036f9b20a4e..e4ca2bfe2bd 100644 --- a/python/ql/test/query-tests/Statements/asserts/assert.py +++ b/python/ql/test/query-tests/Statements/asserts/assert.py @@ -61,8 +61,8 @@ def ok_assert_false(x): if x: assert 0==1, "Ok" -class TestCase: - pass +from unittest import TestCase + class MyTest(TestCase): def test_ok_assert_in_test(self, x): diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll index 127d9ca5122..69563a3eab4 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll @@ -299,7 +299,7 @@ private class AccessPathRange extends AccessPath::Range { bindingset[token] API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) { // API graphs use the same label for arguments and parameters. An edge originating from a - // use-node represents be an argument, and an edge originating from a def-node represents a parameter. + // use-node represents an argument, and an edge originating from a def-node represents a parameter. // We just map both to the same thing. token.getName() = ["Argument", "Parameter"] and result = node.getParameter(AccessPath::parseIntUnbounded(token.getAnArgument())) diff --git a/swift/README.md b/swift/README.md index 17d1906a007..67ca199842c 100644 --- a/swift/README.md +++ b/swift/README.md @@ -13,7 +13,7 @@ bazel run //swift:create-extractor-pack which will install `swift/extractor-pack`. Using `--search-path=swift/extractor-pack` will then pick up the Swift extractor. You can also use -`--search-path=.`, as the extractor pack is mentioned in the root `.codeql-workspace.json`. +`--search-path=.`, as the extractor pack is mentioned in the root `codeql-workspace.yml`. Notice you can run `bazel run :create-extractor-pack` if you already are in the `swift` directory. diff --git a/swift/codegen/schema.yml b/swift/codegen/schema.yml index ebdece63965..dbb220bfd43 100644 --- a/swift/codegen/schema.yml +++ b/swift/codegen/schema.yml @@ -390,6 +390,8 @@ InOutExpr: KeyPathApplicationExpr: _extends: Expr + base: Expr + key_path: Expr KeyPathDotExpr: _extends: Expr diff --git a/swift/extractor/visitors/ExprVisitor.h b/swift/extractor/visitors/ExprVisitor.h index d65e824becc..6977a91d1a4 100644 --- a/swift/extractor/visitors/ExprVisitor.h +++ b/swift/extractor/visitors/ExprVisitor.h @@ -468,7 +468,7 @@ class ExprVisitor : public AstVisitorBase { auto pathLabel = dispatcher_.fetchLabel(path); dispatcher_.emit(KeyPathExprParsedPathsTrap{label, pathLabel}); } - if (auto root = expr->getParsedPath()) { + if (auto root = expr->getParsedRoot()) { auto rootLabel = dispatcher_.fetchLabel(root); dispatcher_.emit(KeyPathExprParsedRootsTrap{label, rootLabel}); } @@ -509,6 +509,22 @@ class ExprVisitor : public AstVisitorBase { dispatcher_.emit(IfExprsTrap{label, condLabel, thenLabel, elseLabel}); } + void visitKeyPathDotExpr(swift::KeyPathDotExpr* expr) { + auto label = dispatcher_.assignNewLabel(expr); + dispatcher_.emit(KeyPathDotExprsTrap{label}); + } + + void visitKeyPathApplicationExpr(swift::KeyPathApplicationExpr* expr) { + auto label = dispatcher_.assignNewLabel(expr); + assert(expr->getBase() && "KeyPathApplicationExpr has getBase()"); + assert(expr->getKeyPath() && "KeyPathApplicationExpr has getKeyPath()"); + + auto baseLabel = dispatcher_.fetchLabel(expr->getBase()); + auto keyPathLabel = dispatcher_.fetchLabel(expr->getKeyPath()); + + dispatcher_.emit(KeyPathApplicationExprsTrap{label, baseLabel, keyPathLabel}); + } + private: TrapLabel emitArgument(const swift::Argument& arg) { auto argLabel = dispatcher_.createLabel(); diff --git a/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowElements.qll b/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowElements.qll index b5557c71e86..38d7a8c3e9a 100644 --- a/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowElements.qll +++ b/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowElements.qll @@ -3,6 +3,7 @@ private import swift cached newtype TControlFlowElement = TAstElement(AstNode n) or + TFuncDeclElement(AbstractFunctionDecl func) { func.hasBody() } or TPropertyGetterElement(Decl accessor, Expr ref) { isPropertyGetterElement(accessor, ref) } or TPropertySetterElement(AccessorDecl accessor, AssignExpr assign) { isPropertySetterElement(accessor, assign) @@ -161,3 +162,13 @@ class PropertyObserverElement extends ControlFlowElement, TPropertyObserverEleme AssignExpr getAssignExpr() { result = assign } } + +class FuncDeclElement extends ControlFlowElement, TFuncDeclElement { + AbstractFunctionDecl func; + + FuncDeclElement() { this = TFuncDeclElement(func) } + + override string toString() { result = func.toString() } + + override Location getLocation() { result = func.getLocation() } +} diff --git a/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll b/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll index 4db8afaca7a..4d7cbe90ad0 100644 --- a/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll +++ b/swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImpl.qll @@ -48,15 +48,15 @@ module CfgScope { private class BodyStmtCallableScope extends Range_ instanceof AbstractFunctionDecl { final override predicate entry(ControlFlowElement first) { - exists(Stmts::BraceStmtTree tree | - tree.getAst() = super.getBody() and - tree.firstInner(first) + exists(Decls::FuncDeclTree tree | + tree.getAst() = this and + first = tree ) } final override predicate exit(ControlFlowElement last, Completion c) { - exists(Stmts::BraceStmtTree tree | - tree.getAst() = super.getBody() and + exists(Decls::FuncDeclTree tree | + tree.getAst() = this and tree.last(last, c) ) } @@ -881,6 +881,21 @@ module Decls { ) } } + + class FuncDeclTree extends StandardPreOrderTree, TFuncDeclElement { + AbstractFunctionDecl ast; + + FuncDeclTree() { this = TFuncDeclElement(ast) } + + AbstractFunctionDecl getAst() { result = ast } + + final override ControlFlowElement getChildElement(int i) { + result.asAstNode() = ast.getParam(i) + or + result.asAstNode() = ast.getBody() and + i = ast.getNumberOfParams() + } + } } module Exprs { diff --git a/swift/ql/lib/codeql/swift/dataflow/Ssa.qll b/swift/ql/lib/codeql/swift/dataflow/Ssa.qll index 9053428d4b0..0f67b0faa5a 100644 --- a/swift/ql/lib/codeql/swift/dataflow/Ssa.qll +++ b/swift/ql/lib/codeql/swift/dataflow/Ssa.qll @@ -64,6 +64,29 @@ module Ssa { a = bb.getNode(i).getNode().asAstNode() and value.getNode().asAstNode() = a.getSource() ) + or + exists(VarDecl var, BasicBlock bb, int blockIndex, PatternBindingDecl pbd | + this.definesAt(var, bb, blockIndex) and + pbd.getAPattern() = bb.getNode(blockIndex).getNode().asAstNode() and + value.getNode().asAstNode() = var.getParentInitializer() + ) } } + + cached + class PhiDefinition extends Definition, SsaImplCommon::PhiNode { + cached + override Location getLocation() { + exists(BasicBlock bb, int i | + this.definesAt(_, bb, i) and + result = bb.getLocation() + ) + } + + cached + Definition getPhiInput(BasicBlock bb) { SsaImplCommon::phiHasInputFromBlock(this, result, bb) } + + cached + Definition getAPhiInput() { result = this.getPhiInput(_) } + } } diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll index 0fe0acad457..d416f16b583 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowDispatch.qll @@ -1,5 +1,6 @@ private import swift private import DataFlowPrivate +private import DataFlowPublic newtype TReturnKind = TNormalReturnKind() @@ -42,47 +43,34 @@ class DataFlowCallable extends TDataFlowCallable { * A call. This includes calls from source code, as well as call(back)s * inside library callables with a flow summary. */ -class DataFlowCall extends TDataFlowCall { +class DataFlowCall extends ExprNode { + DataFlowCall() { this.asExpr() instanceof CallExpr } + /** Gets the enclosing callable. */ DataFlowCallable getEnclosingCallable() { none() } - - /** Gets a textual representation of this call. */ - string toString() { none() } - - /** Gets the location of this call. */ - Location getLocation() { none() } - - /** - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries). - */ - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } } cached private module Cached { cached - newtype TDataFlowCallable = TODO_TDataFlowCallable() - - cached - newtype TDataFlowCall = TODO_TDataFlowCall() + newtype TDataFlowCallable = TDataFlowFunc(FuncDecl func) /** Gets a viable run-time target for the call `call`. */ cached - DataFlowCallable viableCallable(DataFlowCall call) { none() } + DataFlowCallable viableCallable(DataFlowCall call) { + result = TDataFlowFunc(call.asExpr().(CallExpr).getStaticTarget()) + } cached - newtype TArgumentPosition = TODO_TArgumentPosition() + newtype TArgumentPosition = + TThisArgument() or + // we rely on default exprs generated in the caller for ordering + TPositionalArgument(int n) { n = any(Argument arg).getIndex() } cached - newtype TParameterPosition = TODO_TParameterPosition() + newtype TParameterPosition = + TThisParameter() or + TPositionalParameter(int n) { n = any(Argument arg).getIndex() } } import Cached @@ -105,12 +93,25 @@ class ParameterPosition extends TParameterPosition { string toString() { none() } } +class PositionalParameterPosition extends ParameterPosition, TPositionalParameter { + int getIndex() { this = TPositionalParameter(result) } +} + /** An argument position. */ class ArgumentPosition extends TArgumentPosition { /** Gets a textual representation of this position. */ string toString() { none() } } +class PositionalArgumentPosition extends ArgumentPosition, TPositionalArgument { + int getIndex() { this = TPositionalArgument(result) } +} + /** Holds if arguments at position `apos` match parameters at position `ppos`. */ pragma[inline] -predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { none() } +predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { + ppos instanceof TThisParameter and + apos instanceof TThisArgument + or + ppos.(PositionalParameterPosition).getIndex() = apos.(PositionalArgumentPosition).getIndex() +} diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll index fc3321d2263..3de5e0dfdf9 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll @@ -3,6 +3,8 @@ private import DataFlowPublic private import DataFlowDispatch private import codeql.swift.controlflow.CfgNodes private import codeql.swift.dataflow.Ssa +private import codeql.swift.controlflow.BasicBlocks +private import codeql.swift.dataflow.internal.SsaImplCommon as SsaImpl /** Gets the callable in which this node occurs. */ DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() } @@ -31,12 +33,25 @@ private class ExprNodeImpl extends ExprNode, NodeImpl { override Location getLocationImpl() { result = expr.getLocation() } override string toStringImpl() { result = expr.toString() } + + override DataFlowCallable getEnclosingCallable() { result = TDataFlowFunc(expr.getScope()) } } private class SsaDefinitionNodeImpl extends SsaDefinitionNode, NodeImpl { override Location getLocationImpl() { result = def.getLocation() } override string toStringImpl() { result = def.toString() } + + override DataFlowCallable getEnclosingCallable() { + result = TDataFlowFunc(def.getBasicBlock().getScope()) + } +} + +private predicate localFlowSsaInput(Node nodeFrom, Ssa::Definition def, Ssa::Definition next) { + exists(BasicBlock bb, int i | SsaImpl::lastRefRedef(def, bb, i, next) | + def.definesAt(_, bb, i) and + def = nodeFrom.asDefinition() + ) } /** A collection of cached types and predicates to be evaluated in the same stage. */ @@ -45,7 +60,6 @@ private module Cached { cached newtype TNode = TExprNode(ExprCfgNode e) or - TNormalParameterNode(ParamDecl p) or TSsaDefinitionNode(Ssa::Definition def) private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) { @@ -60,6 +74,9 @@ private module Cached { or // use-use flow def.adjacentReadPair(nodeFrom.getCfgNode(), nodeTo.getCfgNode()) + or + // step from previous read to Phi node + localFlowSsaInput(nodeFrom, def, nodeTo.asDefinition()) ) } @@ -93,18 +110,29 @@ private module ParameterNodes { predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { none() } } - class NormalParameterNode extends ParameterNodeImpl, TNormalParameterNode { + class NormalParameterNode extends ParameterNodeImpl, SsaDefinitionNode { ParamDecl param; - NormalParameterNode() { this = TNormalParameterNode(param) } + NormalParameterNode() { + exists(BasicBlock bb, int i | + super.asDefinition().definesAt(param, bb, i) and + bb.getNode(i).getNode().asAstNode() = param + ) + } override Location getLocationImpl() { result = param.getLocation() } override string toStringImpl() { result = param.toString() } override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { - none() // TODO + exists(FuncDecl f, int index | + c = TDataFlowFunc(f) and + f.getParam(index) = param and + pos = TPositionalParameter(index) + ) } + + override DataFlowCallable getEnclosingCallable() { isParameterOf(result, _) } } } @@ -119,7 +147,16 @@ abstract class ArgumentNode extends Node { final DataFlowCall getCall() { this.argumentOf(result, _) } } -private module ArgumentNodes { } +private module ArgumentNodes { + class NormalArgumentNode extends ExprNode, ArgumentNode { + NormalArgumentNode() { exists(CallExpr call | call.getAnArgument().getExpr() = this.asExpr()) } + + override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) { + call.asExpr().(CallExpr).getArgument(pos.(PositionalArgumentPosition).getIndex()).getExpr() = + this.asExpr() + } + } +} import ArgumentNodes @@ -129,7 +166,13 @@ abstract class ReturnNode extends Node { abstract ReturnKind getKind(); } -private module ReturnNodes { } +private module ReturnNodes { + class ReturnReturnNode extends ReturnNode, ExprNode { + ReturnReturnNode() { exists(ReturnStmt stmt | stmt.getResult() = this.asExpr()) } + + override ReturnKind getKind() { result instanceof NormalReturnKind } + } +} import ReturnNodes @@ -139,7 +182,13 @@ abstract class OutNode extends Node { abstract DataFlowCall getCall(ReturnKind kind); } -private module OutNodes { } +private module OutNodes { + class CallOutNode extends OutNode, DataFlowCall { + override DataFlowCall getCall(ReturnKind kind) { + result = this and kind instanceof NormalReturnKind + } + } +} import OutNodes @@ -169,7 +218,9 @@ class DataFlowType extends TDataFlowType { } /** Gets the type of `n` used for type pruning. */ -DataFlowType getNodeType(NodeImpl n) { none() } +DataFlowType getNodeType(NodeImpl n) { + any() // return the singleton DataFlowType until we support type pruning for Swift +} /** Gets a string representation of a `DataFlowType`. */ string ppReprType(DataFlowType t) { result = t.toString() } diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll index 70afd2651e1..67a184193d0 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPublic.qll @@ -69,7 +69,7 @@ class ExprNode extends Node, TExprNode { * The value of a parameter at function entry, viewed as a node in a data * flow graph. */ -class ParameterNode extends Node, TNormalParameterNode instanceof ParameterNodeImpl { } +class ParameterNode extends Node, SsaDefinitionNode instanceof ParameterNodeImpl { } /** */ @@ -98,7 +98,7 @@ class PostUpdateNode extends Node instanceof PostUpdateNodeImpl { } /** Gets a node corresponding to expression `e`. */ -ExprNode exprNode(DataFlowExpr e) { none() } +ExprNode exprNode(DataFlowExpr e) { result.asExpr() = e } /** * Gets the node corresponding to the value of parameter `p` at function entry. diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll b/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll index bd7373935aa..458e0b3a2e6 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll @@ -27,6 +27,10 @@ predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) v.getParentPattern() = pattern and certain = true ) + or + v instanceof ParamDecl and + bb.getNode(i).getNode().asAstNode() = v and + certain = true } private predicate isLValue(DeclRefExpr ref) { any(AssignExpr assign).getDest() = ref } diff --git a/swift/ql/lib/codeql/swift/elements/expr/Argument.qll b/swift/ql/lib/codeql/swift/elements/expr/Argument.qll index c2a25b877a4..88c11b14013 100644 --- a/swift/ql/lib/codeql/swift/elements/expr/Argument.qll +++ b/swift/ql/lib/codeql/swift/elements/expr/Argument.qll @@ -1,5 +1,8 @@ private import codeql.swift.generated.expr.Argument +private import codeql.swift.elements.expr.ApplyExpr class Argument extends ArgumentBase { override string toString() { result = this.getLabel() + ": " + this.getExpr().toString() } + + int getIndex() { any(ApplyExpr apply).getArgument(result) = this } } diff --git a/swift/ql/lib/codeql/swift/generated/expr/KeyPathApplicationExpr.qll b/swift/ql/lib/codeql/swift/generated/expr/KeyPathApplicationExpr.qll index e746c44943b..900145c26bd 100644 --- a/swift/ql/lib/codeql/swift/generated/expr/KeyPathApplicationExpr.qll +++ b/swift/ql/lib/codeql/swift/generated/expr/KeyPathApplicationExpr.qll @@ -3,4 +3,18 @@ import codeql.swift.elements.expr.Expr class KeyPathApplicationExprBase extends @key_path_application_expr, Expr { override string getAPrimaryQlClass() { result = "KeyPathApplicationExpr" } + + Expr getBase() { + exists(Expr x | + key_path_application_exprs(this, x, _) and + result = x.resolve() + ) + } + + Expr getKeyPath() { + exists(Expr x | + key_path_application_exprs(this, _, x) and + result = x.resolve() + ) + } } diff --git a/swift/ql/lib/swift.dbscheme b/swift/ql/lib/swift.dbscheme index 47edbb550e8..5305c94a75e 100644 --- a/swift/ql/lib/swift.dbscheme +++ b/swift/ql/lib/swift.dbscheme @@ -888,7 +888,9 @@ in_out_exprs( ); key_path_application_exprs( - unique int id: @key_path_application_expr + unique int id: @key_path_application_expr, + int base: @expr ref, + int key_path: @expr ref ); key_path_dot_exprs( diff --git a/swift/ql/test/extractor-tests/expressions/all.expected b/swift/ql/test/extractor-tests/expressions/all.expected index d8094e7f54f..5ec593d4a09 100644 --- a/swift/ql/test/extractor-tests/expressions/all.expected +++ b/swift/ql/test/extractor-tests/expressions/all.expected @@ -125,6 +125,7 @@ | expressions.swift:54:1:54:1 | _ | | expressions.swift:54:1:54:8 | ... = ... | | expressions.swift:54:5:54:8 | #keyPath(...) | +| expressions.swift:54:6:54:8 | TBD (UnresolvedDotExpr) | | expressions.swift:58:16:58:16 | 1234 | | expressions.swift:59:1:59:1 | unsafeFunction | | expressions.swift:59:1:59:34 | call to unsafeFunction | @@ -232,3 +233,15 @@ | expressions.swift:138:10:138:17 | ...[...] | | expressions.swift:138:13:138:13 | 3 | | expressions.swift:138:16:138:16 | 4 | +| expressions.swift:152:26:152:26 | a | +| expressions.swift:152:26:152:47 | \\...[...] | +| expressions.swift:152:37:152:37 | keyPathInt | +| expressions.swift:153:24:153:24 | a | +| expressions.swift:153:24:153:43 | \\...[...] | +| expressions.swift:153:35:153:35 | keyPathB | +| expressions.swift:154:22:154:22 | a | +| expressions.swift:154:22:154:41 | \\...[...] | +| expressions.swift:154:22:154:56 | \\...[...] | +| expressions.swift:154:33:154:33 | keyPathB | +| expressions.swift:154:52:154:55 | #keyPath(...) | +| expressions.swift:154:53:154:55 | TBD (UnresolvedDotExpr) | diff --git a/swift/ql/test/extractor-tests/expressions/expressions.swift b/swift/ql/test/extractor-tests/expressions/expressions.swift index f294a5a285c..bc720abe70f 100644 --- a/swift/ql/test/extractor-tests/expressions/expressions.swift +++ b/swift/ql/test/extractor-tests/expressions/expressions.swift @@ -136,4 +136,20 @@ func testProperties(hp : inout HasProperty) -> Int { var w = hp.normalField hp[1] = 2 return hp[3, 4] +} + +struct B { + var x : Int +} + +struct A { + var b : B + var bs : [B] + var mayB : B? +} + +func test(a : A, keyPathInt : WritableKeyPath, keyPathB : WritableKeyPath) { + var apply_keyPathInt = a[keyPath: keyPathInt] + var apply_keyPathB = a[keyPath: keyPathB] + var nested_apply = a[keyPath: keyPathB][keyPath: \B.x] } \ No newline at end of file diff --git a/swift/ql/test/extractor-tests/expressions/semantics.expected b/swift/ql/test/extractor-tests/expressions/semantics.expected index 5f07b18715f..ea49c8e7ead 100644 --- a/swift/ql/test/extractor-tests/expressions/semantics.expected +++ b/swift/ql/test/extractor-tests/expressions/semantics.expected @@ -68,3 +68,9 @@ | expressions.swift:137:3:137:7 | ...[...] | OrdinarySemantics | | expressions.swift:138:10:138:10 | hp | OrdinarySemantics | | expressions.swift:138:10:138:17 | ...[...] | OrdinarySemantics | +| expressions.swift:152:26:152:26 | a | OrdinarySemantics | +| expressions.swift:152:37:152:37 | keyPathInt | OrdinarySemantics | +| expressions.swift:153:24:153:24 | a | OrdinarySemantics | +| expressions.swift:153:35:153:35 | keyPathB | OrdinarySemantics | +| expressions.swift:154:22:154:22 | a | OrdinarySemantics | +| expressions.swift:154:33:154:33 | keyPathB | OrdinarySemantics | diff --git a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected index 1a6485f84f6..6a45f334141 100644 --- a/swift/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/swift/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -1,12 +1,15 @@ cfg.swift: # 5| enter returnZero -#-----| -> 0 +#-----| -> returnZero # 5| exit returnZero # 5| exit returnZero (normal) #-----| -> exit returnZero +# 5| returnZero +#-----| -> 0 + # 5| return ... #-----| return -> exit returnZero (normal) @@ -14,13 +17,19 @@ cfg.swift: #-----| -> return ... # 15| enter isZero -#-----| -> == +#-----| -> isZero # 15| exit isZero # 15| exit isZero (normal) #-----| -> exit isZero +# 15| isZero +#-----| -> x + +# 15| x +#-----| -> == + # 15| return ... #-----| return -> exit isZero (normal) @@ -44,13 +53,19 @@ cfg.swift: #-----| -> ... call to == ... # 17| enter mightThrow -#-----| -> guard ... else { ... } +#-----| -> mightThrow # 17| exit mightThrow # 17| exit mightThrow (normal) #-----| -> exit mightThrow +# 17| mightThrow +#-----| -> x + +# 17| x +#-----| -> guard ... else { ... } + # 18| guard ... else { ... } #-----| -> >= @@ -164,13 +179,19 @@ cfg.swift: #-----| -> ... call to + ... # 26| enter tryCatch -#-----| -> do { ... } catch { ... } +#-----| -> tryCatch # 26| exit tryCatch # 26| exit tryCatch (normal) #-----| -> exit tryCatch +# 26| tryCatch +#-----| -> x + +# 26| x +#-----| -> do { ... } catch { ... } + # 27| do { ... } catch { ... } #-----| -> mightThrow @@ -363,28 +384,48 @@ cfg.swift: # 42| 0 #-----| -> return ... +# 45| createClosure1 +#-----| -> s + # 45| enter createClosure1 -#-----| -> { ... } +#-----| -> createClosure1 # 45| exit createClosure1 # 45| exit createClosure1 (normal) #-----| -> exit createClosure1 +# 45| s +#-----| -> { ... } + # 46| return ... #-----| return -> exit createClosure1 (normal) # 46| { ... } #-----| -> return ... +# 51| createClosure2 +#-----| -> x + +# 51| enter createClosure2 +#-----| -> createClosure2 + +# 51| x + # 52| enter f -#-----| -> + +#-----| -> f # 52| exit f # 52| exit f (normal) #-----| -> exit f +# 52| f +#-----| -> y + +# 52| y +#-----| -> + + # 53| return ... #-----| return -> exit f (normal) @@ -407,23 +448,32 @@ cfg.swift: # 53| y #-----| -> ... call to + ... +# 58| createClosure3 +#-----| -> x + # 58| enter createClosure3 -#-----| -> { ... } +#-----| -> createClosure3 # 58| exit createClosure3 # 58| exit createClosure3 (normal) #-----| -> exit createClosure3 +# 58| x +#-----| -> { ... } + # 59| return ... #-----| return -> exit createClosure3 (normal) # 59| { ... } #-----| -> return ... -# 64| enter callClosures +# 64| callClosures #-----| -> x1 +# 64| enter callClosures +#-----| -> callClosures + # 64| exit callClosures # 64| exit callClosures (normal) @@ -502,13 +552,19 @@ cfg.swift: #-----| -> call to ... # 70| enter maybeParseInt -#-----| -> n +#-----| -> maybeParseInt # 70| exit maybeParseInt # 70| exit maybeParseInt (normal) #-----| -> exit maybeParseInt +# 70| maybeParseInt +#-----| -> s + +# 70| s +#-----| -> n + # 71| var ... = ... #-----| -> n @@ -546,13 +602,16 @@ cfg.swift: #-----| -> (Int?) ... # 75| enter forceAndBackToOptional -#-----| -> nBang +#-----| -> forceAndBackToOptional # 75| exit forceAndBackToOptional # 75| exit forceAndBackToOptional (normal) #-----| -> exit forceAndBackToOptional +# 75| forceAndBackToOptional +#-----| -> nBang + # 76| var ... = ... #-----| -> nBang @@ -629,6 +688,9 @@ cfg.swift: #-----| -> ... call to + ... # 81| enter testInOut +#-----| -> testInOut + +# 81| testInOut #-----| -> temp # 82| var ... = ... @@ -642,14 +704,20 @@ cfg.swift: # 82| 10 #-----| -> var ... = ... -# 84| enter add +# 84| add #-----| -> a +# 84| enter add +#-----| -> add + # 84| exit add # 84| exit add (normal) #-----| -> exit add +# 84| a +#-----| -> a + # 85| a #-----| -> + @@ -678,14 +746,20 @@ cfg.swift: # 85| 1 #-----| -> ... call to + ... -# 88| enter addOptional +# 88| addOptional #-----| -> a +# 88| enter addOptional +#-----| -> addOptional + # 88| exit addOptional # 88| exit addOptional (normal) #-----| -> exit addOptional +# 88| a +#-----| -> a + # 89| a #-----| -> nil @@ -695,21 +769,44 @@ cfg.swift: # 89| nil #-----| -> ... = ... +# 98| enter init +#-----| -> init + +# 98| exit init + +# 98| exit init (normal) +#-----| -> exit init + +# 98| init +#-----| -> { ... } + +# 98| { ... } +#-----| -> exit init (normal) + # 99| enter get +#-----| -> get # 99| exit get # 99| exit get (normal) #-----| -> exit get +# 99| get + +# 100| deinit +#-----| -> n + # 100| enter deinit -#-----| -> self +#-----| -> deinit # 100| exit deinit # 100| exit deinit (normal) #-----| -> exit deinit +# 100| n +#-----| -> self + # 101| .myInt #-----| -> n @@ -726,13 +823,16 @@ cfg.swift: #-----| return -> exit deinit (normal) # 104| enter getMyInt -#-----| -> self +#-----| -> getMyInt # 104| exit getMyInt # 104| exit getMyInt (normal) #-----| -> exit getMyInt +# 104| getMyInt +#-----| -> self + # 105| return ... #-----| return -> exit getMyInt (normal) @@ -743,13 +843,25 @@ cfg.swift: #-----| -> getter for .myInt # 109| enter testMemberRef -#-----| -> c +#-----| -> testMemberRef # 109| exit testMemberRef # 109| exit testMemberRef (normal) #-----| -> exit testMemberRef +# 109| testMemberRef +#-----| -> param + +# 109| param +#-----| -> inoutParam + +# 109| inoutParam +#-----| -> opt + +# 109| opt +#-----| -> c + # 110| var ... = ... #-----| -> c @@ -1233,13 +1345,19 @@ cfg.swift: #-----| -> TBD (DotSelfExpr) # 137| enter patterns -#-----| -> ... +#-----| -> patterns # 137| exit patterns # 137| exit patterns (normal) #-----| -> exit patterns +# 137| patterns +#-----| -> x + +# 137| x +#-----| -> ... + # 138| for ... in ... { ... } #-----| non-empty -> _ #-----| empty -> switch x { ... } @@ -1336,13 +1454,19 @@ cfg.swift: #-----| -> return ... # 163| enter testDefer -#-----| -> defer { ... } +#-----| -> testDefer # 163| exit testDefer # 163| exit testDefer (normal) #-----| -> exit testDefer +# 163| testDefer +#-----| -> x + +# 163| x +#-----| -> defer { ... } + # 165| defer { ... } #-----| -> defer { ... } @@ -1468,13 +1592,19 @@ cfg.swift: #-----| -> [...] # 181| enter m1 -#-----| -> if ... then { ... } else { ... } +#-----| -> m1 # 181| exit m1 # 181| exit m1 (normal) #-----| -> exit m1 +# 181| m1 +#-----| -> x + +# 181| x +#-----| -> if ... then { ... } else { ... } + # 182| if ... then { ... } else { ... } #-----| -> > @@ -1679,13 +1809,19 @@ cfg.swift: #-----| -> [...] # 193| enter m2 -#-----| -> if ... then { ... } +#-----| -> m2 # 193| exit m2 # 193| exit m2 (normal) #-----| -> exit m2 +# 193| m2 +#-----| -> b + +# 193| b +#-----| -> if ... then { ... } + # 194| if ... then { ... } #-----| -> b @@ -1709,13 +1845,19 @@ cfg.swift: #-----| -> return ... # 200| enter m3 -#-----| -> if ... then { ... } +#-----| -> m3 # 200| exit m3 # 200| exit m3 (normal) #-----| -> exit m3 +# 200| m3 +#-----| -> x + +# 200| x +#-----| -> if ... then { ... } + # 201| if ... then { ... } #-----| -> < @@ -1837,13 +1979,25 @@ cfg.swift: #-----| -> (Int) ... # 210| enter m4 -#-----| -> b1 +#-----| -> m4 # 210| exit m4 # 210| exit m4 (normal) #-----| -> exit m4 +# 210| m4 +#-----| -> b1 + +# 210| b1 +#-----| -> b2 + +# 210| b2 +#-----| -> b3 + +# 210| b3 +#-----| -> b1 + # 211| return ... #-----| return -> exit m4 (normal) @@ -1880,14 +2034,20 @@ cfg.swift: # 211| !b2 || !b3 #-----| -> ... ? ... : ... +# 214| conversionsInSplitEntry +#-----| -> b + # 214| enter conversionsInSplitEntry -#-----| -> if ... then { ... } else { ... } +#-----| -> conversionsInSplitEntry # 214| exit conversionsInSplitEntry # 214| exit conversionsInSplitEntry (normal) #-----| -> exit conversionsInSplitEntry +# 214| b +#-----| -> if ... then { ... } else { ... } + # 215| if ... then { ... } else { ... } #-----| -> b @@ -1926,9 +2086,12 @@ cfg.swift: # 219| !b #-----| -> return ... -# 223| enter constant_condition +# 223| constant_condition #-----| -> if ... then { ... } +# 223| enter constant_condition +#-----| -> constant_condition + # 223| exit constant_condition # 223| exit constant_condition (normal) @@ -1975,14 +2138,20 @@ cfg.swift: # 225| [...] #-----| -> [...] +# 229| empty_else +#-----| -> b + # 229| enter empty_else -#-----| -> if ... then { ... } else { ... } +#-----| -> empty_else # 229| exit empty_else # 229| exit empty_else (normal) #-----| -> exit empty_else +# 229| b +#-----| -> if ... then { ... } else { ... } + # 230| if ... then { ... } else { ... } #-----| -> b @@ -2053,14 +2222,23 @@ cfg.swift: #-----| -> (Any) ... #-----| -> (TBD (ProtocolCompositionType)) ... +# 237| disjunct +#-----| -> b1 + # 237| enter disjunct -#-----| -> if ... then { ... } +#-----| -> disjunct # 237| exit disjunct # 237| exit disjunct (normal) #-----| -> exit disjunct +# 237| b1 +#-----| -> b2 + +# 237| b2 +#-----| -> if ... then { ... } + # 238| if ... then { ... } #-----| -> b1 @@ -2115,14 +2293,23 @@ cfg.swift: #-----| -> (Any) ... #-----| -> (TBD (ProtocolCompositionType)) ... +# 243| binaryExprs +#-----| -> a + # 243| enter binaryExprs -#-----| -> c +#-----| -> binaryExprs # 243| exit binaryExprs # 243| exit binaryExprs (normal) #-----| -> exit binaryExprs +# 243| a +#-----| -> b + +# 243| b +#-----| -> c + # 244| var ... = ... #-----| -> c @@ -2572,13 +2759,22 @@ cfg.swift: #-----| -> ... call to >= ... # 262| enter interpolatedString -#-----| -> "..." +#-----| -> interpolatedString # 262| exit interpolatedString # 262| exit interpolatedString (normal) #-----| -> exit interpolatedString +# 262| interpolatedString +#-----| -> x + +# 262| x +#-----| -> y + +# 262| y +#-----| -> "..." + # 263| return ... #-----| return -> exit interpolatedString (normal) @@ -2586,13 +2782,16 @@ cfg.swift: #-----| -> return ... # 266| enter testSubscriptExpr -#-----| -> a +#-----| -> testSubscriptExpr # 266| exit testSubscriptExpr # 266| exit testSubscriptExpr (normal) #-----| -> exit testSubscriptExpr +# 266| testSubscriptExpr +#-----| -> a + # 267| var ... = ... #-----| -> a @@ -3783,13 +3982,19 @@ cfg.swift: #-----| -> getter for ...[...] # 299| enter loop1 -#-----| -> while ... { ... } +#-----| -> loop1 # 299| exit loop1 # 299| exit loop1 (normal) #-----| -> exit loop1 +# 299| loop1 +#-----| -> x + +# 299| x +#-----| -> while ... { ... } + # 300| while ... { ... } #-----| -> >= @@ -3874,13 +4079,19 @@ cfg.swift: #-----| -> ... call to -= ... # 306| enter loop2 -#-----| -> while ... { ... } +#-----| -> loop2 # 306| exit loop2 # 306| exit loop2 (normal) #-----| -> exit loop2 +# 306| loop2 +#-----| -> x + +# 306| x +#-----| -> while ... { ... } + # 307| while ... { ... } #-----| -> >= @@ -4086,13 +4297,19 @@ cfg.swift: #-----| -> [...] # 321| enter labeledLoop -#-----| -> while ... { ... } +#-----| -> labeledLoop # 321| exit labeledLoop # 321| exit labeledLoop (normal) #-----| -> exit labeledLoop +# 321| labeledLoop +#-----| -> x + +# 321| x +#-----| -> while ... { ... } + # 322| while ... { ... } #-----| -> >= @@ -4328,13 +4545,19 @@ cfg.swift: #-----| -> [...] # 338| enter testRepeat -#-----| -> repeat { ... } while ... +#-----| -> testRepeat # 338| exit testRepeat # 338| exit testRepeat (normal) #-----| -> exit testRepeat +# 338| testRepeat +#-----| -> x + +# 338| x +#-----| -> repeat { ... } while ... + # 339| repeat { ... } while ... #-----| -> print @@ -4416,13 +4639,16 @@ cfg.swift: #-----| -> ... call to >= ... # 345| enter loop_with_identity_expr -#-----| -> x +#-----| -> loop_with_identity_expr # 345| exit loop_with_identity_expr # 345| exit loop_with_identity_expr (normal) #-----| -> exit loop_with_identity_expr +# 345| loop_with_identity_expr +#-----| -> x + # 346| var ... = ... #-----| -> x @@ -4489,21 +4715,44 @@ cfg.swift: # 348| 1 #-----| -> ... call to += ... +# 352| enter init +#-----| -> init + +# 352| exit init + +# 352| exit init (normal) +#-----| -> exit init + +# 352| init +#-----| -> { ... } + +# 352| { ... } +#-----| -> exit init (normal) + # 353| enter get +#-----| -> get # 353| exit get # 353| exit get (normal) #-----| -> exit get +# 353| get + +# 354| deinit +#-----| -> arg + # 354| enter deinit -#-----| -> self +#-----| -> deinit # 354| exit deinit # 354| exit deinit (normal) #-----| -> exit deinit +# 354| arg +#-----| -> self + # 355| .c #-----| -> arg @@ -4520,13 +4769,16 @@ cfg.swift: #-----| return -> exit deinit (normal) # 358| enter getOptional -#-----| -> self +#-----| -> getOptional # 358| exit getOptional # 358| exit getOptional (normal) #-----| -> exit getOptional +# 358| getOptional +#-----| -> self + # 359| return ... #-----| return -> exit getOptional (normal) @@ -4537,13 +4789,19 @@ cfg.swift: #-----| -> getter for .c # 363| enter testOptional -#-----| -> getMyInt +#-----| -> testOptional # 363| exit testOptional # 363| exit testOptional (normal) #-----| -> exit testOptional +# 363| testOptional +#-----| -> c + +# 363| c +#-----| -> getMyInt + # 364| return ... #-----| return -> exit testOptional (normal) @@ -4583,13 +4841,22 @@ cfg.swift: #-----| -> getOptional # 367| enter testCapture -#-----| -> z +#-----| -> testCapture # 367| exit testCapture # 367| exit testCapture (normal) #-----| -> exit testCapture +# 367| testCapture +#-----| -> x + +# 367| x +#-----| -> y + +# 367| y +#-----| -> z + # 368| return ... #-----| return -> exit testCapture (normal) @@ -4634,13 +4901,19 @@ cfg.swift: #-----| -> var ... = ... # 373| enter testTupleElement -#-----| -> + +#-----| -> testTupleElement # 373| exit testTupleElement # 373| exit testTupleElement (normal) #-----| -> exit testTupleElement +# 373| testTupleElement +#-----| -> t + +# 373| t +#-----| -> + + # 374| return ... #-----| return -> exit testTupleElement (normal) @@ -4737,18 +5010,37 @@ cfg.swift: # 377| cfg.Derived #-----| -> #... +# 377| enter init +#-----| -> init + +# 377| exit init + +# 377| exit init (normal) +#-----| -> exit init + +# 377| init +#-----| -> { ... } + +# 377| { ... } +#-----| -> exit init (normal) + +# 377| deinit + # 377| enter deinit -#-----| -> _unimplementedInitializer +#-----| -> deinit # 377| exit deinit # 377| exit deinit (normal) #-----| -> exit deinit -# 378| enter deinit +# 378| deinit #-----| -> call to ... #-----| -> TBD (OtherConstructorDeclRefExpr) +# 378| enter deinit +#-----| -> deinit + # 378| exit deinit # 378| exit deinit (normal) @@ -4778,14 +5070,20 @@ cfg.swift: # 380| return #-----| return -> exit deinit (normal) +# 383| doWithoutCatch +#-----| -> x + # 383| enter doWithoutCatch -#-----| -> do { ... } +#-----| -> doWithoutCatch # 383| exit doWithoutCatch # 383| exit doWithoutCatch (normal) #-----| -> exit doWithoutCatch +# 383| x +#-----| -> do { ... } + # 384| do { ... } #-----| -> mightThrow @@ -4830,13 +5128,18 @@ cfg.swift: # 386| [...] #-----| -> [...] -# 394| enter (unnamed function decl) +# 394| (unnamed function decl) #-----| -> yield ... #-----| -> TBD (YieldStmt) +# 394| enter (unnamed function decl) +#-----| -> (unnamed function decl) + # 394| enter get +#-----| -> get # 394| enter set +#-----| -> set # 394| exit (unnamed function decl) @@ -4853,15 +5156,25 @@ cfg.swift: # 394| exit set (normal) #-----| -> exit set +# 394| get + +# 394| set +#-----| -> value + +# 394| value + # 394| yield ... #-----| -> exit (unnamed function decl) (normal) # 394| TBD (YieldStmt) #-----| -> exit (unnamed function decl) (normal) -# 395| enter deinit +# 395| deinit #-----| -> self +# 395| enter deinit +#-----| -> deinit + # 395| exit deinit # 395| exit deinit (normal) @@ -4883,13 +5196,16 @@ cfg.swift: #-----| return -> exit deinit (normal) # 399| enter init -#-----| -> self +#-----| -> init # 399| exit init # 399| exit init (normal) #-----| -> exit init +# 399| init +#-----| -> self + # 400| .field #-----| -> 0 @@ -4902,14 +5218,23 @@ cfg.swift: # 400| 0 #-----| -> ... = ... -# 404| enter dictionaryLiteral +# 404| dictionaryLiteral #-----| -> x +# 404| enter dictionaryLiteral +#-----| -> dictionaryLiteral + # 404| exit dictionaryLiteral # 404| exit dictionaryLiteral (normal) #-----| -> exit dictionaryLiteral +# 404| x +#-----| -> y + +# 404| y +#-----| -> x + # 405| return ... #-----| return -> exit dictionaryLiteral (normal) diff --git a/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected new file mode 100644 index 00000000000..fe79e3ab862 --- /dev/null +++ b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.expected @@ -0,0 +1,44 @@ +edges +| test.swift:6:19:6:26 | call to source : | test.swift:7:15:7:15 | t1 | +| test.swift:6:19:6:26 | call to source : | test.swift:9:15:9:15 | t1 | +| test.swift:6:19:6:26 | call to source : | test.swift:10:15:10:15 | t2 | +| test.swift:25:20:25:27 | call to source : | test.swift:29:18:29:21 | WriteDef : | +| test.swift:25:20:25:27 | call to source : | test.swift:29:18:29:21 | x : | +| test.swift:26:26:26:33 | call to source : | test.swift:29:26:29:29 | WriteDef : | +| test.swift:26:26:26:33 | call to source : | test.swift:29:26:29:29 | y : | +| test.swift:29:18:29:21 | WriteDef : | test.swift:30:15:30:15 | x | +| test.swift:29:18:29:21 | x : | test.swift:30:15:30:15 | x | +| test.swift:29:26:29:29 | WriteDef : | test.swift:31:15:31:15 | y | +| test.swift:29:26:29:29 | y : | test.swift:31:15:31:15 | y | +| test.swift:35:12:35:19 | call to source : | test.swift:39:15:39:29 | call to callee_source | +| test.swift:43:19:43:26 | call to source : | test.swift:50:15:50:15 | t | +nodes +| test.swift:6:19:6:26 | call to source : | semmle.label | call to source : | +| test.swift:7:15:7:15 | t1 | semmle.label | t1 | +| test.swift:9:15:9:15 | t1 | semmle.label | t1 | +| test.swift:10:15:10:15 | t2 | semmle.label | t2 | +| test.swift:25:20:25:27 | call to source : | semmle.label | call to source : | +| test.swift:26:26:26:33 | call to source : | semmle.label | call to source : | +| test.swift:29:18:29:21 | WriteDef : | semmle.label | WriteDef : | +| test.swift:29:18:29:21 | WriteDef : | semmle.label | x : | +| test.swift:29:18:29:21 | x : | semmle.label | WriteDef : | +| test.swift:29:18:29:21 | x : | semmle.label | x : | +| test.swift:29:26:29:29 | WriteDef : | semmle.label | WriteDef : | +| test.swift:29:26:29:29 | WriteDef : | semmle.label | y : | +| test.swift:29:26:29:29 | y : | semmle.label | WriteDef : | +| test.swift:29:26:29:29 | y : | semmle.label | y : | +| test.swift:30:15:30:15 | x | semmle.label | x | +| test.swift:31:15:31:15 | y | semmle.label | y | +| test.swift:35:12:35:19 | call to source : | semmle.label | call to source : | +| test.swift:39:15:39:29 | call to callee_source | semmle.label | call to callee_source | +| test.swift:43:19:43:26 | call to source : | semmle.label | call to source : | +| test.swift:50:15:50:15 | t | semmle.label | t | +subpaths +#select +| test.swift:6:19:6:26 | call to source : | test.swift:7:15:7:15 | t1 | +| test.swift:6:19:6:26 | call to source : | test.swift:9:15:9:15 | t1 | +| test.swift:6:19:6:26 | call to source : | test.swift:10:15:10:15 | t2 | +| test.swift:25:20:25:27 | call to source : | test.swift:30:15:30:15 | x | +| test.swift:26:26:26:33 | call to source : | test.swift:31:15:31:15 | y | +| test.swift:35:12:35:19 | call to source : | test.swift:39:15:39:29 | call to callee_source | +| test.swift:43:19:43:26 | call to source : | test.swift:50:15:50:15 | t | diff --git a/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.ql b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.ql new file mode 100644 index 00000000000..be4f604a4f7 --- /dev/null +++ b/swift/ql/test/library-tests/dataflow/dataflow/DataFlow.ql @@ -0,0 +1,24 @@ +import swift +import codeql.swift.dataflow.DataFlow +import DataFlow::PathGraph + +class TestConfiguration extends DataFlow::Configuration { + TestConfiguration() { this = "TestConfiguration" } + + override predicate isSource(DataFlow::Node src) { + src.asExpr().(CallExpr).getStaticTarget().getName() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + exists(CallExpr sinkCall | + sinkCall.getStaticTarget().getName() = "sink" and + sinkCall.getAnArgument().getExpr() = sink.asExpr() + ) + } + + override int explorationLimit() { result = 100 } +} + +from DataFlow::PathNode src, DataFlow::PathNode sink, TestConfiguration test +where test.hasFlowPath(src, sink) +select src, sink diff --git a/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected b/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected index 92fe3d7565c..8c4f15886a1 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected +++ b/swift/ql/test/library-tests/dataflow/dataflow/LocalFlow.expected @@ -1,6 +1,5 @@ -| file://:0:0:0:0 | Phi | test.swift:15:15:15:15 | t2 | -| file://:0:0:0:0 | Phi | test.swift:21:15:21:15 | t1 | | test.swift:6:9:6:13 | WriteDef | test.swift:7:15:7:15 | t1 | +| test.swift:6:19:6:26 | call to source | test.swift:6:9:6:13 | WriteDef | | test.swift:7:15:7:15 | t1 | test.swift:8:10:8:10 | t1 | | test.swift:8:5:8:10 | WriteDef | test.swift:10:15:10:15 | t2 | | test.swift:8:10:8:10 | t1 | test.swift:8:5:8:10 | WriteDef | @@ -8,7 +7,29 @@ | test.swift:9:15:9:15 | t1 | test.swift:11:8:11:8 | t1 | | test.swift:12:9:12:14 | WriteDef | test.swift:13:19:13:19 | t2 | | test.swift:12:14:12:14 | 0 | test.swift:12:9:12:14 | WriteDef | +| test.swift:15:5:15:5 | Phi | test.swift:15:15:15:15 | t2 | | test.swift:15:15:15:15 | t2 | test.swift:19:14:19:14 | t2 | +| test.swift:17:5:17:10 | WriteDef | test.swift:18:11:18:11 | Phi | | test.swift:17:10:17:10 | 0 | test.swift:17:5:17:10 | WriteDef | +| test.swift:18:11:18:11 | Phi | test.swift:19:9:19:14 | WriteDef | +| test.swift:18:11:18:11 | Phi | test.swift:21:15:21:15 | t1 | +| test.swift:19:9:19:14 | WriteDef | test.swift:18:11:18:11 | Phi | | test.swift:19:14:19:14 | t2 | test.swift:19:9:19:14 | WriteDef | | test.swift:19:14:19:14 | t2 | test.swift:19:14:19:14 | t2 | +| test.swift:29:18:29:21 | WriteDef | test.swift:30:15:30:15 | x | +| test.swift:29:18:29:21 | x | test.swift:30:15:30:15 | x | +| test.swift:29:26:29:29 | WriteDef | test.swift:31:15:31:15 | y | +| test.swift:29:26:29:29 | y | test.swift:31:15:31:15 | y | +| test.swift:42:16:42:19 | WriteDef | test.swift:45:8:45:8 | b | +| test.swift:42:16:42:19 | b | test.swift:45:8:45:8 | b | +| test.swift:43:9:43:13 | WriteDef | test.swift:46:13:46:13 | t1 | +| test.swift:43:19:43:26 | call to source | test.swift:43:9:43:13 | WriteDef | +| test.swift:46:9:46:13 | WriteDef | test.swift:50:5:50:5 | Phi | +| test.swift:46:13:46:13 | t1 | test.swift:46:9:46:13 | WriteDef | +| test.swift:48:9:48:13 | WriteDef | test.swift:50:5:50:5 | Phi | +| test.swift:48:13:48:13 | 1 | test.swift:48:9:48:13 | WriteDef | +| test.swift:50:5:50:5 | Phi | test.swift:50:15:50:15 | t | +| test.swift:58:9:58:12 | WriteDef | test.swift:59:15:59:15 | x | +| test.swift:58:18:58:18 | 0 | test.swift:58:9:58:12 | WriteDef | +| test.swift:59:15:59:15 | x | test.swift:60:23:60:23 | x | +| test.swift:60:23:60:23 | x | test.swift:61:15:61:15 | x | diff --git a/swift/ql/test/library-tests/dataflow/dataflow/test.swift b/swift/ql/test/library-tests/dataflow/dataflow/test.swift index d20fecb54d7..113db2dfea7 100644 --- a/swift/ql/test/library-tests/dataflow/dataflow/test.swift +++ b/swift/ql/test/library-tests/dataflow/dataflow/test.swift @@ -20,3 +20,43 @@ func intraprocedural_with_local_flow() -> Void { } sink(arg: t1) } + +func caller_source() -> Void { + callee_sink(x: source(), y: 1) + callee_sink(x: 1, y: source()) +} + +func callee_sink(x: Int, y: Int) -> Void { + sink(arg: x) + sink(arg: y) +} + +func callee_source() -> Int { + return source() +} + +func caller_sink() -> Void { + sink(arg: callee_source()) +} + +func branching(b: Bool) -> Void { + var t1: Int = source() + var t: Int = 0 + if(b) { + t = t1; + } else { + t = 1; + } + sink(arg: t) +} + +func inoutSource(arg: inout Int) -> Void { + arg = source() +} + +func inoutUser() { + var x: Int = 0 + sink(arg: x) + inoutSource(arg: &x) + sink(arg: x) +} \ No newline at end of file