diff --git a/.sbtopts b/.sbtopts index b95704d9..8a06137a 100644 --- a/.sbtopts +++ b/.sbtopts @@ -1,2 +1,2 @@ -J-Xms3G --J-Xmx4G +-J-Xmx16G diff --git a/README.md b/README.md index b1f5fcce..14ba73d1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Code Hierarchy Exploration Net (chen) is an advanced exploration toolkit for you - Java >= 21 - Python >= 3.10 -- Node.js >= 18 (To run [atom](https://github.com/AppThreat/atom)) +- Node.js >= 20 (To run [atom](https://github.com/AppThreat/atom)) - Minimum 16GB RAM ## Getting started @@ -147,9 +147,9 @@ Refer to the documentation site to learn more about the commands. ## Languages supported -- C/C++ (Requires Java 21 or above) +- C/C++ - H (C/C++ Header files alone) -- Java (Requires compilation) - 8 to 17 +- Java (Requires compilation) - 8 to 21 - Jar - Android APK (Requires Android SDK. Set the environment variable `ANDROID_HOME`) - JavaScript diff --git a/build.sbt b/build.sbt index 3334e3ee..0a08734b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,7 @@ name := "chen" ThisBuild / organization := "io.appthreat" -ThisBuild / version := "2.1.0" -ThisBuild / scalaVersion := "3.4.1" +ThisBuild / version := "2.1.1" +ThisBuild / scalaVersion := "3.4.2" val cpgVersion = "1.0.0" diff --git a/ci/Dockerfile b/ci/Dockerfile index b8f694d6..b60081a3 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -12,8 +12,8 @@ LABEL maintainer="appthreat" \ org.opencontainers.docker.cmd="docker run --rm -v /tmp:/tmp -v $HOME:$HOME -v $(pwd):/app:rw -it ghcr.io/appthreat/chen chennai" ARG JAVA_VERSION=22.0.1-graalce -ARG MAVEN_VERSION=3.9.6 -ARG GRADLE_VERSION=8.7 +ARG MAVEN_VERSION=3.9.8 +ARG GRADLE_VERSION=8.8 ENV JAVA_VERSION=$JAVA_VERSION \ MAVEN_VERSION=$MAVEN_VERSION \ diff --git a/codemeta.json b/codemeta.json index c99f7f45..332b070e 100644 --- a/codemeta.json +++ b/codemeta.json @@ -7,7 +7,7 @@ "downloadUrl": "https://github.com/AppThreat/chen", "issueTracker": "https://github.com/AppThreat/chen/issues", "name": "chen", - "version": "2.1.0", + "version": "2.1.1", "description": "Code Hierarchy Exploration Net (chen) is an advanced exploration toolkit for your application source code and its dependency hierarchy.", "applicationCategory": "code-analysis", "keywords": [ diff --git a/console/build.sbt b/console/build.sbt index 4f6c9437..44a2fbba 100644 --- a/console/build.sbt +++ b/console/build.sbt @@ -4,7 +4,7 @@ enablePlugins(JavaAppPackaging) val ScoptVersion = "4.1.0" val CaskVersion = "0.9.2" -val CirceVersion = "0.14.6" +val CirceVersion = "0.14.9" val ZeroturnaroundVersion = "1.17" dependsOn( @@ -24,7 +24,7 @@ libraryDependencies ++= Seq( "io.circe" %% "circe-generic" % CirceVersion, "io.circe" %% "circe-parser" % CirceVersion, "org.zeroturnaround" % "zt-zip" % ZeroturnaroundVersion, - "com.lihaoyi" %% "os-lib" % "0.10.0", + "com.lihaoyi" %% "os-lib" % "0.10.2", "com.lihaoyi" %% "pprint" % "0.9.0", "com.lihaoyi" %% "cask" % CaskVersion, "dev.scalapy" %% "scalapy-core" % "0.5.3", diff --git a/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/language/package.scala b/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/language/package.scala index 399ac644..c5bac851 100644 --- a/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/language/package.scala +++ b/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/language/package.scala @@ -6,6 +6,8 @@ import io.appthreat.dataflowengineoss.language.nodemethods.{ ExtendedCfgNodeMethods } import io.shiftleft.codepropertygraph.generated.nodes.* +import io.shiftleft.semanticcpg.language.* +import scala.language.implicitConversions package object language: @@ -26,4 +28,17 @@ package object language: implicit def toDdgNodeDotSingle(method: Method): DdgNodeDot = new DdgNodeDot(Iterator.single(method)) + + implicit def toExtendedPathsTrav[NodeType <: Path](traversal: IterableOnce[NodeType]) + : PassesExt = + new PassesExt(traversal.iterator) + + class PassesExt(traversal: Iterator[Path]): + + def passes(trav: Iterator[AstNode] => Iterator[?]): Iterator[Path] = + traversal.filter(_.elements.exists(_.start.where(trav).nonEmpty)) + + def passesNot(trav: Iterator[AstNode] => Iterator[?]): Iterator[Path] = + traversal.filter(_.elements.forall(_.start.where(trav).isEmpty)) + end language diff --git a/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/EdgeValidator.scala b/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/EdgeValidator.scala index e9b863e6..2ab75eda 100644 --- a/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/EdgeValidator.scala +++ b/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/EdgeValidator.scala @@ -24,6 +24,11 @@ object EdgeValidator: case (childNode: Expression, parentNode) if isCallRetval(parentNode) || !isValidEdgeToExpression(parentNode, childNode) => false + case (childNode: Call, parentNode: Expression) + if isCallRetval(childNode) && childNode.argument.contains(parentNode) => + // e.g. foo(x), but there are semantics for `foo` that don't taint its return value + // in which case we don't want `x` to taint `foo(x)`. + false case (childNode: Expression, parentNode: Expression) if parentNode.isArgToSameCallWith( childNode diff --git a/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/ReachingDefProblem.scala b/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/ReachingDefProblem.scala index 10816445..f6b6a7f3 100644 --- a/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/ReachingDefProblem.scala +++ b/dataflowengineoss/src/main/scala/io/appthreat/dataflowengineoss/passes/reachingdef/ReachingDefProblem.scala @@ -165,7 +165,7 @@ class ReachingDefTransferFunction(flowGraph: ReachingDefFlowGraph) val gen: Map[StoredNode, mutable.BitSet] = initGen(method).withDefaultValue(mutable.BitSet()) - val kill: Map[StoredNode, Set[Definition]] = + val kill: Map[StoredNode, mutable.BitSet] = initKill(method, gen).withDefaultValue(mutable.BitSet()) /** For a given flow graph node `n` and set of definitions, apply the transfer function to @@ -226,8 +226,8 @@ class ReachingDefTransferFunction(flowGraph: ReachingDefFlowGraph) */ private def initKill( method: Method, - gen: Map[StoredNode, Set[Definition]] - ): Map[StoredNode, Set[Definition]] = + gen: Map[StoredNode, mutable.BitSet] + ): Map[StoredNode, mutable.BitSet] = val allIdentifiers: Map[String, List[CfgNode]] = val results = mutable.Map.empty[String, List[CfgNode]] @@ -266,44 +266,45 @@ class ReachingDefTransferFunction(flowGraph: ReachingDefFlowGraph) * gen(call). */ private def killsForGens( - genOfCall: Set[Definition], + genOfCall: mutable.BitSet, allIdentifiers: Map[String, List[CfgNode]], allCalls: Map[String, List[Call]] - ): Set[Definition] = + ): mutable.BitSet = - def definitionsOfSameVariable(definition: Definition): Set[Definition] = + def definitionsOfSameVariable(definition: Definition): Iterator[Definition] = val definedNodes = flowGraph.numberToNode(definition) match case param: MethodParameterIn => - allIdentifiers(param.name) + allIdentifiers(param.name).iterator .filter(x => x.id != param.id) case identifier: Identifier => - val sameIdentifiers = allIdentifiers(identifier.name) + val sameIdentifiers = allIdentifiers(identifier.name).iterator .filter(x => x.id != identifier.id) /** Killing an identifier should also kill field accesses on that identifier. * For example, a reassignment `x = new Box()` should kill any previous calls * to `x.value`, `x.length()`, etc. */ - val sameObjects: Iterable[Call] = allCalls.values.flatten + val sameObjects: Iterator[Call] = allCalls.valuesIterator.flatten .filter(_.name == Operators.fieldAccess) .filter(_.ast.isIdentifier.nameExact(identifier.name).nonEmpty) sameIdentifiers ++ sameObjects case call: Call => - allCalls(call.code) + allCalls(call.code).iterator .filter(x => x.id != call.id) - case _ => Set() + case _ => Iterator.empty definedNodes // It can happen that the CFG is broken and contains isolated nodes, // in which case they are not in `nodeToNumber`. Let's filter those. .collect { case x if nodeToNumber.contains(x) => Definition.fromNode(x, nodeToNumber) - }.toSet + } end definitionsOfSameVariable - genOfCall.flatMap { definition => - definitionsOfSameVariable(definition) - } + val res = mutable.BitSet() + for definition <- genOfCall do + res.addAll(definitionsOfSameVariable(definition)) + res end killsForGens end ReachingDefTransferFunction diff --git a/meta.yaml b/meta.yaml index 7444c66a..f4346683 100644 --- a/meta.yaml +++ b/meta.yaml @@ -1,4 +1,4 @@ -{% set version = "2.1.0" %} +{% set version = "2.1.1" %} package: name: chen diff --git a/platform/frontends/c2cpg/build.sbt b/platform/frontends/c2cpg/build.sbt index c9c11c47..e230a670 100644 --- a/platform/frontends/c2cpg/build.sbt +++ b/platform/frontends/c2cpg/build.sbt @@ -4,8 +4,8 @@ dependsOn(Projects.semanticcpg, Projects.dataflowengineoss % Test, Projects.x2cp libraryDependencies ++= Seq( "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4", - "org.eclipse.platform" % "org.eclipse.equinox.common" % "3.19.0", - "org.eclipse.platform" % "org.eclipse.core.resources" % "3.20.100" excludeAll( + "org.eclipse.platform" % "org.eclipse.equinox.common" % "3.19.100", + "org.eclipse.platform" % "org.eclipse.core.resources" % "3.20.200" excludeAll( ExclusionRule(organization = "com.ibm.icu", name = "icu4j"), ExclusionRule(organization = "org.eclipse.platform", name = "org.eclipse.jface"), ExclusionRule(organization = "org.eclipse.platform", name = "org.eclipse.jface.text") diff --git a/platform/frontends/c2cpg/lib/org.eclipse.cdt.core_8.4.0.202402110645.jar b/platform/frontends/c2cpg/lib/org.eclipse.cdt.core_8.4.201.202406290148.jar similarity index 85% rename from platform/frontends/c2cpg/lib/org.eclipse.cdt.core_8.4.0.202402110645.jar rename to platform/frontends/c2cpg/lib/org.eclipse.cdt.core_8.4.201.202406290148.jar index aed28249..4e3a4707 100644 Binary files a/platform/frontends/c2cpg/lib/org.eclipse.cdt.core_8.4.0.202402110645.jar and b/platform/frontends/c2cpg/lib/org.eclipse.cdt.core_8.4.201.202406290148.jar differ diff --git a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala index 747ae14a..eb0480f6 100644 --- a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala +++ b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstCreatorHelper.scala @@ -16,13 +16,14 @@ import org.eclipse.cdt.core.dom.ast.c.{ import org.eclipse.cdt.core.dom.ast.cpp.* import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator import org.eclipse.cdt.internal.core.dom.parser.c.CASTArrayRangeDesignator -import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.{EvalBinding, EvalMemberAccess} import org.eclipse.cdt.internal.core.dom.parser.cpp.* +import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.{EvalBinding, EvalMemberAccess} import org.eclipse.cdt.internal.core.model.ASTStringUtil import java.nio.file.{Path, Paths} import scala.annotation.nowarn import scala.collection.mutable +import scala.util.Try object AstCreatorHelper: @@ -36,11 +37,30 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): import AstCreatorHelper.* - private var usedVariablePostfix: Int = 0 - private val IncludeKeyword = "include" - - protected def isIncludedNode(node: IASTNode): Boolean = fileName(node) != filename + // Sadly, there is no predefined List / Enum of this within Eclipse CDT: + private val reservedTypeKeywords: List[String] = + List( + "const", + "static", + "volatile", + "restrict", + "extern", + "typedef", + "inline", + "constexpr", + "auto", + "virtual", + "enum", + "struct", + "interface", + "class", + "naked", + "export", + "module", + "import" + ) + private var usedVariablePostfix: Int = 0 protected def uniqueName(target: String, name: String, fullName: String): (String, String) = if name.isEmpty && (fullName.isEmpty || fullName.endsWith(".")) then @@ -51,39 +71,20 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): else (name, fullName) - private def fileOffsetTable(node: IASTNode): Array[Int] = - val path = SourceFiles.toAbsolutePath(fileName(node), config.inputPath) - file2OffsetTable.computeIfAbsent(path, _ => genFileOffsetTable(Paths.get(path))) - - private def genFileOffsetTable(absolutePath: Path): Array[Int] = - val asCharArray = IOUtils.readLinesInFile(absolutePath).mkString("\n").toCharArray - val offsets = mutable.ArrayBuffer.empty[Int] - - for i <- Range(0, asCharArray.length) do - if asCharArray(i) == '\n' then - offsets.append(i + 1) - offsets.toArray - - private def nullSafeFileLocation(node: IASTNode): Option[IASTFileLocation] = - Option(cdtAst.flattenLocationsToFile(node.getNodeLocations)).map(_.asFileLocation()) - - private def nullSafeFileLocationLast(node: IASTNode): Option[IASTFileLocation] = - Option(cdtAst.flattenLocationsToFile(node.getNodeLocations.lastOption.toArray)).map( - _.asFileLocation() - ) - protected def code(node: IASTNode): String = shortenCode(nodeSignature(node)) - protected def fileName(node: IASTNode): String = - val path = nullSafeFileLocation(node).map(_.getFileName).getOrElse(filename) - SourceFiles.toRelativePath(path, config.inputPath) - protected def line(node: IASTNode): Option[Integer] = nullSafeFileLocation(node).map(_.getStartingLineNumber) protected def lineEnd(node: IASTNode): Option[Integer] = nullSafeFileLocationLast(node).map(_.getEndingLineNumber) + protected def column(node: IASTNode): Option[Integer] = + val loc = nullSafeFileLocation(node) + loc.map { x => + offsetToColumn(node, x.getNodeOffset) + } + private def offsetToColumn(node: IASTNode, offset: Int): Int = val table = fileOffsetTable(node) val index = java.util.Arrays.binarySearch(table, offset) @@ -95,11 +96,25 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): val column = offset - lineStartOffset + 1 column - protected def column(node: IASTNode): Option[Integer] = - val loc = nullSafeFileLocation(node) - loc.map { x => - offsetToColumn(node, x.getNodeOffset) - } + private def fileOffsetTable(node: IASTNode): Array[Int] = + val path = SourceFiles.toAbsolutePath(fileName(node), config.inputPath) + file2OffsetTable.computeIfAbsent(path, _ => genFileOffsetTable(Paths.get(path))) + + private def genFileOffsetTable(absolutePath: Path): Array[Int] = + val asCharArray = IOUtils.readLinesInFile(absolutePath).mkString("\n").toCharArray + val offsets = mutable.ArrayBuffer.empty[Int] + + for i <- Range(0, asCharArray.length) do + if asCharArray(i) == '\n' then + offsets.append(i + 1) + offsets.toArray + + protected def fileName(node: IASTNode): String = + val path = nullSafeFileLocation(node).map(_.getFileName).getOrElse(filename) + SourceFiles.toRelativePath(path, config.inputPath) + + private def nullSafeFileLocation(node: IASTNode): Option[IASTFileLocation] = + Option(cdtAst.flattenLocationsToFile(node.getNodeLocations)).map(_.asFileLocation()) protected def columnEnd(node: IASTNode): Option[Integer] = val loc = nullSafeFileLocation(node) @@ -112,27 +127,10 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): CGlobal.usedTypes.putIfAbsent(fixedTypeName, true) fixedTypeName - // Sadly, there is no predefined List / Enum of this within Eclipse CDT: - private val reservedTypeKeywords: List[String] = - List( - "const", - "static", - "volatile", - "restrict", - "extern", - "typedef", - "inline", - "constexpr", - "auto", - "virtual", - "enum", - "struct", - "interface", - "class", - "naked", - "export", - "module", - "import" + protected def fixQualifiedName(name: String): String = + name.stripPrefix(Defines.qualifiedNameSeparator).replace( + Defines.qualifiedNameSeparator, + "." ) protected def cleanType(rawType: String, stripKeywords: Boolean = true): String = @@ -162,9 +160,8 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): val anonType = s"${uniqueName("type", "", "")._1}${t.substring(0, t.indexOf("{"))}${t.substring(t.indexOf("}") + 1)}" anonType.replace(" ", "") - case t if t.startsWith("[") && t.endsWith("]") => Defines.anyTypeName - case t if t.contains(Defines.qualifiedNameSeparator) => - fixQualifiedName(t).split(".").lastOption.getOrElse(Defines.anyTypeName) + case t if t.startsWith("[") && t.endsWith("]") => Defines.anyTypeName + case t if t.contains(Defines.qualifiedNameSeparator) => fixQualifiedName(t) case t if t.startsWith("unsigned ") => "unsigned " + t.substring(9).replace(" ", "") case t if t.contains("[") && t.contains("]") => t.replace(" ", "") case t if t.contains("*") => t.replace(" ", "") @@ -177,8 +174,8 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): import org.eclipse.cdt.core.dom.ast.ASTSignatureUtil.getNodeSignature node match case f: CPPASTFieldReference => - f.getFieldOwner.getEvaluation match - case evaluation: EvalBinding => + safeGetEvaluation(f.getFieldOwner) match + case Some(evaluation: EvalBinding) => cleanType(evaluation.getType.toString, stripKeywords) case _ => cleanType( ASTTypeUtil.getType(f.getFieldOwner.getExpressionType), @@ -198,10 +195,10 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): case a: IASTArrayDeclarator if ASTTypeUtil.getNodeType(a).contains(" [") => cleanType(ASTTypeUtil.getNodeType(node)) case s: CPPASTIdExpression => - s.getEvaluation match - case evaluation: EvalMemberAccess => + safeGetEvaluation(s) match + case Some(evaluation: EvalMemberAccess) => cleanType(evaluation.getOwnerType.toString, stripKeywords) - case evalBinding: EvalBinding => + case Some(evalBinding: EvalBinding) => evalBinding.getBinding match case m: CPPMethod => cleanType(fullName(m.getDefinition)) case _ => cleanType(ASTTypeUtil.getNodeType(s), stripKeywords) @@ -220,18 +217,19 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): cleanType(ASTTypeUtil.getType(l.getExpressionType)) case e: IASTExpression => cleanType(ASTTypeUtil.getNodeType(e), stripKeywords) + case c: ICPPASTConstructorInitializer + if c.getParent.isInstanceOf[ICPPASTConstructorChainInitializer] => + cleanType( + fullName(c.getParent.asInstanceOf[ + ICPPASTConstructorChainInitializer + ].getMemberInitializerId), + stripKeywords + ) case _ => cleanType(getNodeSignature(node), stripKeywords) end match end typeFor - private def notHandledText(node: IASTNode): String = - s"""Node '${node.getClass.getSimpleName}' not handled yet! - | Code: '${node.getRawSignature}' - | File: '$filename' - | Line: ${line(node).getOrElse(-1)} - | """.stripMargin - protected def notHandledYet(node: IASTNode): Ast = if !node.isInstanceOf[IASTProblem] && !node.isInstanceOf[IASTProblemHolder] then val text = notHandledText(node) @@ -258,12 +256,6 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): protected def dereferenceTypeFullName(fullName: String): String = fullName.replace("*", "") - protected def fixQualifiedName(name: String): String = - name.stripPrefix(Defines.qualifiedNameSeparator).replace( - Defines.qualifiedNameSeparator, - "." - ) - protected def isQualifiedName(name: String): Boolean = name.startsWith(Defines.qualifiedNameSeparator) @@ -342,6 +334,7 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): case d: IASTIdExpression => ASTStringUtil.getSimpleName(d.getName) case _: IASTTranslationUnit => "" case u: IASTUnaryExpression => nodeSignature(u.getOperand) + case x: ICPPASTQualifiedName => ASTStringUtil.getQualifiedName(x) case other if other.getParent != null => fullName(other.getParent) case other if other != null => notHandledYet(other); "" case null => "" @@ -393,23 +386,6 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): name end shortName - private def pointersAsString( - spec: IASTDeclSpecifier, - parentDecl: IASTDeclarator, - stripKeywords: Boolean - ): String = - val tpe = typeFor(spec, stripKeywords) - val pointers = parentDecl.getPointerOperators - val arr = parentDecl match - case p: IASTArrayDeclarator => - p.getArrayModifiers.toList.map(_.getRawSignature).mkString - case _ => "" - if pointers.isEmpty then s"$tpe$arr" - else - val refs = - "*" * (pointers.length - pointers.count(_.isInstanceOf[ICPPASTReferenceOperator])) - s"$tpe$arr$refs".strip() - protected def astsForDependenciesAndImports(iASTTranslationUnit: IASTTranslationUnit) : Seq[Ast] = val allIncludes = iASTTranslationUnit.getIncludeDirectives.toList.filterNot(isIncludedNode) @@ -422,6 +398,8 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): Ast(importNode) } + protected def isIncludedNode(node: IASTNode): Boolean = fileName(node) != filename + protected def astsForComments(iASTTranslationUnit: IASTTranslationUnit): Seq[Ast] = if config.includeComments then iASTTranslationUnit.getComments.toList.filterNot(isIncludedNode).map(comment => @@ -430,63 +408,6 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): else Seq.empty - private def astForDecltypeSpecifier(decl: ICPPASTDecltypeSpecifier): Ast = - val op = ".typeOf" - val cpgUnary = callNode(decl, nodeSignature(decl), op, op, DispatchTypes.STATIC_DISPATCH) - val operand = nullSafeAst(decl.getDecltypeExpression) - callAst(cpgUnary, List(operand)) - - private def astForCASTDesignatedInitializer(d: ICASTDesignatedInitializer): Ast = - val node = blockNode(d, Defines.empty, Defines.voidTypeName) - scope.pushNewScope(node) - val op = Operators.assignment - val calls = withIndex(d.getDesignators) { (des, o) => - val callNode_ = - callNode(d, nodeSignature(d), op, op, DispatchTypes.STATIC_DISPATCH) - .argumentIndex(o) - val left = astForNode(des) - val right = astForNode(d.getOperand) - callAst(callNode_, List(left, right)) - } - scope.popScope() - blockAst(node, calls.toList) - - private def astForCPPASTDesignatedInitializer(d: ICPPASTDesignatedInitializer): Ast = - val node = blockNode(d, Defines.empty, Defines.voidTypeName) - scope.pushNewScope(node) - val op = Operators.assignment - val calls = withIndex(d.getDesignators) { (des, o) => - val callNode_ = - callNode(d, nodeSignature(d), op, op, DispatchTypes.STATIC_DISPATCH) - .argumentIndex(o) - val left = astForNode(des) - val right = astForNode(d.getOperand) - callAst(callNode_, List(left, right)) - } - scope.popScope() - blockAst(node, calls.toList) - - private def astForCPPASTConstructorInitializer(c: ICPPASTConstructorInitializer): Ast = - val name = ".constructorInitializer" - val callNode_ = - callNode(c, nodeSignature(c), name, name, DispatchTypes.STATIC_DISPATCH) - val args = c.getArguments.toList.map(a => astForNode(a)) - callAst(callNode_, args) - - private def astForCASTArrayRangeDesignator(des: CASTArrayRangeDesignator): Ast = - val op = Operators.arrayInitializer - val callNode_ = callNode(des, nodeSignature(des), op, op, DispatchTypes.STATIC_DISPATCH) - val floorAst = nullSafeAst(des.getRangeFloor) - val ceilingAst = nullSafeAst(des.getRangeCeiling) - callAst(callNode_, List(floorAst, ceilingAst)) - - private def astForCPPASTArrayRangeDesignator(des: CPPASTArrayRangeDesignator): Ast = - val op = Operators.arrayInitializer - val callNode_ = callNode(des, nodeSignature(des), op, op, DispatchTypes.STATIC_DISPATCH) - val floorAst = nullSafeAst(des.getRangeFloor) - val ceilingAst = nullSafeAst(des.getRangeCeiling) - callAst(callNode_, List(floorAst, ceilingAst)) - protected def astForNode(node: IASTNode): Ast = if config.includeFunctionBodies then astForNodeFull(node) else astForNodePartial(node) @@ -570,4 +491,94 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode): case _ => Defines.anyTypeName if tpe.isEmpty then Defines.anyTypeName else tpe end typeForDeclSpecifier + + private def nullSafeFileLocationLast(node: IASTNode): Option[IASTFileLocation] = + Option(cdtAst.flattenLocationsToFile(node.getNodeLocations.lastOption.toArray)).map( + _.asFileLocation() + ) + + private def safeGetEvaluation(expr: ICPPASTExpression): Option[ICPPEvaluation] = + // In case of unresolved includes etc. this may fail throwing an unrecoverable exception + Try(expr.getEvaluation).toOption + + private def notHandledText(node: IASTNode): String = + s"""Node '${node.getClass.getSimpleName}' not handled yet! + | Code: '${node.getRawSignature}' + | File: '$filename' + | Line: ${line(node).getOrElse(-1)} + | """.stripMargin + + private def pointersAsString( + spec: IASTDeclSpecifier, + parentDecl: IASTDeclarator, + stripKeywords: Boolean + ): String = + val tpe = typeFor(spec, stripKeywords) + val pointers = parentDecl.getPointerOperators + val arr = parentDecl match + case p: IASTArrayDeclarator => + p.getArrayModifiers.toList.map(_.getRawSignature).mkString + case _ => "" + if pointers.isEmpty then s"$tpe$arr" + else + val refs = + "*" * (pointers.length - pointers.count(_.isInstanceOf[ICPPASTReferenceOperator])) + s"$tpe$arr$refs".strip() + + private def astForDecltypeSpecifier(decl: ICPPASTDecltypeSpecifier): Ast = + val op = ".typeOf" + val cpgUnary = callNode(decl, nodeSignature(decl), op, op, DispatchTypes.STATIC_DISPATCH) + val operand = nullSafeAst(decl.getDecltypeExpression) + callAst(cpgUnary, List(operand)) + + private def astForCASTDesignatedInitializer(d: ICASTDesignatedInitializer): Ast = + val node = blockNode(d, Defines.empty, Defines.voidTypeName) + scope.pushNewScope(node) + val op = Operators.assignment + val calls = withIndex(d.getDesignators) { (des, o) => + val callNode_ = + callNode(d, nodeSignature(d), op, op, DispatchTypes.STATIC_DISPATCH) + .argumentIndex(o) + val left = astForNode(des) + val right = astForNode(d.getOperand) + callAst(callNode_, List(left, right)) + } + scope.popScope() + blockAst(node, calls.toList) + + private def astForCPPASTDesignatedInitializer(d: ICPPASTDesignatedInitializer): Ast = + val node = blockNode(d, Defines.empty, Defines.voidTypeName) + scope.pushNewScope(node) + val op = Operators.assignment + val calls = withIndex(d.getDesignators) { (des, o) => + val callNode_ = + callNode(d, nodeSignature(d), op, op, DispatchTypes.STATIC_DISPATCH) + .argumentIndex(o) + val left = astForNode(des) + val right = astForNode(d.getOperand) + callAst(callNode_, List(left, right)) + } + scope.popScope() + blockAst(node, calls.toList) + + private def astForCPPASTConstructorInitializer(c: ICPPASTConstructorInitializer): Ast = + val name = ".constructorInitializer" + val callNode_ = + callNode(c, nodeSignature(c), name, name, DispatchTypes.STATIC_DISPATCH) + val args = c.getArguments.toList.map(a => astForNode(a)) + callAst(callNode_, args) + + private def astForCASTArrayRangeDesignator(des: CASTArrayRangeDesignator): Ast = + val op = Operators.arrayInitializer + val callNode_ = callNode(des, nodeSignature(des), op, op, DispatchTypes.STATIC_DISPATCH) + val floorAst = nullSafeAst(des.getRangeFloor) + val ceilingAst = nullSafeAst(des.getRangeCeiling) + callAst(callNode_, List(floorAst, ceilingAst)) + + private def astForCPPASTArrayRangeDesignator(des: CPPASTArrayRangeDesignator): Ast = + val op = Operators.arrayInitializer + val callNode_ = callNode(des, nodeSignature(des), op, op, DispatchTypes.STATIC_DISPATCH) + val floorAst = nullSafeAst(des.getRangeFloor) + val ceilingAst = nullSafeAst(des.getRangeCeiling) + callAst(callNode_, List(floorAst, ceilingAst)) end AstCreatorHelper diff --git a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForFunctionsCreator.scala b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForFunctionsCreator.scala index 119f1b0e..73ec30ce 100644 --- a/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForFunctionsCreator.scala +++ b/platform/frontends/c2cpg/src/main/scala/io/appthreat/c2cpg/astcreation/AstForFunctionsCreator.scala @@ -1,19 +1,21 @@ package io.appthreat.c2cpg.astcreation -import io.shiftleft.codepropertygraph.generated.EvaluationStrategies -import io.shiftleft.codepropertygraph.generated.nodes.* +import io.appthreat.x2cpg.datastructures.Stack.* +import io.appthreat.x2cpg.utils.NodeBuilders.newModifierNode import io.appthreat.x2cpg.{Ast, ValidationMode} +import io.shiftleft.codepropertygraph.generated.{EvaluationStrategies, ModifierTypes} +import io.shiftleft.codepropertygraph.generated.nodes.* +import org.apache.commons.lang.StringUtils import org.eclipse.cdt.core.dom.ast.* import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLambdaExpression import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator import org.eclipse.cdt.internal.core.dom.parser.c.{CASTFunctionDeclarator, CASTParameterDeclaration} import org.eclipse.cdt.internal.core.dom.parser.cpp.{ CPPASTFunctionDeclarator, + CPPASTFunctionDefinition, CPPASTParameterDeclaration } import org.eclipse.cdt.internal.core.model.ASTStringUtil -import io.appthreat.x2cpg.datastructures.Stack.* -import org.apache.commons.lang.StringUtils import scala.annotation.tailrec import scala.collection.mutable @@ -23,82 +25,6 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode): private val seenFunctionSignatures = mutable.HashSet.empty[String] - private def createFunctionTypeAndTypeDecl( - node: IASTNode, - method: NewMethod, - methodName: String, - methodFullName: String, - signature: String - ): Ast = - val normalizedName = StringUtils.normalizeSpace(methodName) - val normalizedFullName = StringUtils.normalizeSpace(methodFullName) - - val parentNode: NewTypeDecl = methodAstParentStack.collectFirst { case t: NewTypeDecl => - t - }.getOrElse { - val astParentType = methodAstParentStack.head.label - val astParentFullName = methodAstParentStack.head.properties("FULL_NAME").toString - val typeDeclNode_ = typeDeclNode( - node, - normalizedName, - normalizedFullName, - method.filename, - normalizedName, - astParentType, - astParentFullName - ) - Ast.storeInDiffGraph(Ast(typeDeclNode_), diffGraph) - typeDeclNode_ - } - - method.astParentFullName = parentNode.fullName - method.astParentType = parentNode.label - val functionBinding = NewBinding().name(normalizedName).methodFullName( - normalizedFullName - ).signature(signature) - Ast(functionBinding).withBindsEdge(parentNode, functionBinding).withRefEdge( - functionBinding, - method - ) - end createFunctionTypeAndTypeDecl - - private def parameters(functionNode: IASTNode): Seq[IASTNode] = functionNode match - case arr: IASTArrayDeclarator => parameters(arr.getNestedDeclarator) - case decl: CPPASTFunctionDeclarator => - decl.getParameters.toIndexedSeq ++ parameters(decl.getNestedDeclarator) - case decl: CASTFunctionDeclarator => - decl.getParameters.toIndexedSeq ++ parameters(decl.getNestedDeclarator) - case defn: IASTFunctionDefinition => parameters(defn.getDeclarator) - case lambdaExpression: ICPPASTLambdaExpression => parameters(lambdaExpression.getDeclarator) - case knr: ICASTKnRFunctionDeclarator => knr.getParameterDeclarations.toIndexedSeq - case _: IASTDeclarator => Seq.empty - case other if other != null => notHandledYet(other); Seq.empty - case null => Seq.empty - - @tailrec - private def isVariadic(functionNode: IASTNode): Boolean = functionNode match - case decl: CPPASTFunctionDeclarator => decl.takesVarArgs() - case decl: CASTFunctionDeclarator => decl.takesVarArgs() - case defn: IASTFunctionDefinition => isVariadic(defn.getDeclarator) - case lambdaExpression: ICPPASTLambdaExpression => isVariadic(lambdaExpression.getDeclarator) - case _ => false - - private def parameterListSignature(func: IASTNode): String = - val variadic = if isVariadic(func) then "..." else "" - val elements = parameters(func).map { - case p: IASTParameterDeclaration => typeForDeclSpecifier(p.getDeclSpecifier) - case other => typeForDeclSpecifier(other) - } - s"(${elements.mkString(",")}$variadic)" - - private def setVariadic(parameterNodes: Seq[NewMethodParameterIn], func: IASTNode): Unit = - parameterNodes.lastOption.foreach { - case p: NewMethodParameterIn if isVariadic(func) => - p.isVariadic = true - p.code = s"${p.code}..." - case _ => - } - protected def astForMethodRefForLambda(lambdaExpression: ICPPASTLambdaExpression): Ast = val filename = fileName(lambdaExpression) @@ -173,11 +99,13 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode): end if end astForFunctionDeclarator - private def fullNameWithoutLocation(fullName: String) = fullName.split(":").last - protected def astForFunctionDefinition(funcDef: IASTFunctionDefinition): Ast = - val filename = fileName(funcDef) - val returnType = typeForDeclSpecifier(funcDef.getDeclSpecifier) + val filename = fileName(funcDef) + val returnType = if isCppConstructor(funcDef) then + typeFor(funcDef.asInstanceOf[ + CPPASTFunctionDefinition + ].getMemberInitializers.head.getInitializer) + else typeForDeclSpecifier(funcDef.getDeclSpecifier) val name = shortName(funcDef) val fullname = fullName(funcDef) val templateParams = templateParameters(funcDef).getOrElse("") @@ -196,12 +124,15 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode): parameterNode(p, i) } setVariadic(parameterNodes, funcDef) - + val modifiers = if isCppConstructor(funcDef) then + List(newModifierNode(ModifierTypes.CONSTRUCTOR), newModifierNode(ModifierTypes.PUBLIC)) + else Nil val astForMethod = methodAst( methodNode_, parameterNodes.map(Ast(_)), astForMethodBody(Option(funcDef.getBody)), - newMethodReturnNode(funcDef, registerType(returnType)) + newMethodReturnNode(funcDef, registerType(returnType)), + modifiers = modifiers ) scope.popScope() @@ -212,6 +143,89 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode): astForMethod.merge(typeDeclAst) end astForFunctionDefinition + private def createFunctionTypeAndTypeDecl( + node: IASTNode, + method: NewMethod, + methodName: String, + methodFullName: String, + signature: String + ): Ast = + val normalizedName = StringUtils.normalizeSpace(methodName) + val normalizedFullName = StringUtils.normalizeSpace(methodFullName) + + val parentNode: NewTypeDecl = methodAstParentStack.collectFirst { case t: NewTypeDecl => + t + }.getOrElse { + val astParentType = methodAstParentStack.head.label + val astParentFullName = methodAstParentStack.head.properties("FULL_NAME").toString + val typeDeclNode_ = typeDeclNode( + node, + normalizedName, + normalizedFullName, + method.filename, + normalizedName, + astParentType, + astParentFullName + ) + Ast.storeInDiffGraph(Ast(typeDeclNode_), diffGraph) + typeDeclNode_ + } + + method.astParentFullName = parentNode.fullName + method.astParentType = parentNode.label + val functionBinding = NewBinding().name(normalizedName).methodFullName( + normalizedFullName + ).signature(signature) + Ast(functionBinding).withBindsEdge(parentNode, functionBinding).withRefEdge( + functionBinding, + method + ) + end createFunctionTypeAndTypeDecl + + private def parameters(functionNode: IASTNode): Seq[IASTNode] = functionNode match + case arr: IASTArrayDeclarator => parameters(arr.getNestedDeclarator) + case decl: CPPASTFunctionDeclarator => + decl.getParameters.toIndexedSeq ++ parameters(decl.getNestedDeclarator) + case decl: CASTFunctionDeclarator => + decl.getParameters.toIndexedSeq ++ parameters(decl.getNestedDeclarator) + case defn: IASTFunctionDefinition => parameters(defn.getDeclarator) + case lambdaExpression: ICPPASTLambdaExpression => parameters(lambdaExpression.getDeclarator) + case knr: ICASTKnRFunctionDeclarator => knr.getParameterDeclarations.toIndexedSeq + case _: IASTDeclarator => Seq.empty + case other if other != null => notHandledYet(other); Seq.empty + case null => Seq.empty + + @tailrec + private def isVariadic(functionNode: IASTNode): Boolean = functionNode match + case decl: CPPASTFunctionDeclarator => decl.takesVarArgs() + case decl: CASTFunctionDeclarator => decl.takesVarArgs() + case defn: IASTFunctionDefinition => isVariadic(defn.getDeclarator) + case lambdaExpression: ICPPASTLambdaExpression => isVariadic(lambdaExpression.getDeclarator) + case _ => false + + private def parameterListSignature(func: IASTNode): String = + val variadic = if isVariadic(func) then "..." else "" + val elements = parameters(func).map { + case p: IASTParameterDeclaration => typeForDeclSpecifier(p.getDeclSpecifier) + case other => typeForDeclSpecifier(other) + } + s"(${elements.mkString(",")}$variadic)" + + private def setVariadic(parameterNodes: Seq[NewMethodParameterIn], func: IASTNode): Unit = + parameterNodes.lastOption.foreach { + case p: NewMethodParameterIn if isVariadic(func) => + p.isVariadic = true + p.code = s"${p.code}..." + case _ => + } + + private def fullNameWithoutLocation(fullName: String) = fullName.split(":").last + + private def isCppConstructor(funcDef: IASTFunctionDefinition): Boolean = + funcDef match + case cppFunc: CPPASTFunctionDefinition => cppFunc.getMemberInitializers.nonEmpty + case _ => false + private def parameterNode(parameter: IASTNode, paramIndex: Int): NewMethodParameterIn = val (name, code, tpe, variadic) = parameter match case p: CASTParameterDeclaration => diff --git a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/dataflow/DataFlowTests.scala b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/dataflow/DataFlowTests.scala index 16db15a0..0c8c9417 100644 --- a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/dataflow/DataFlowTests.scala +++ b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/dataflow/DataFlowTests.scala @@ -49,7 +49,7 @@ class DataFlowTests extends DataFlowCodeToCpgSuite { "find flows to `free`" in { val source = cpg.identifier - val sink = cpg.call.name("free") + val sink = cpg.call.name("free").argument(1) sink.reachableByFlows(source).l.map(flowToResultPairs).distinct.size shouldBe 6 } @@ -1252,7 +1252,7 @@ class DataFlowTests extends DataFlowCodeToCpgSuite { "find flows to `free`" in { val source = cpg.identifier - val sink = cpg.call.name("free") + val sink = cpg.call.name("free").argument(1) sink.reachableByFlows(source).l.map(flowToResultPairs).distinct.toSet.size shouldBe 6 } diff --git a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/ClassTypeTests.scala b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/ClassTypeTests.scala index c3caec82..d31956f2 100644 --- a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/ClassTypeTests.scala +++ b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/ClassTypeTests.scala @@ -5,10 +5,10 @@ import io.appthreat.c2cpg.testfixtures.CCodeToCpgSuite import io.shiftleft.semanticcpg.language.* import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal -class ClassTypeTests extends CCodeToCpgSuite(FileDefaults.CPP_EXT) { +class ClassTypeTests extends CCodeToCpgSuite(FileDefaults.CPP_EXT): - "handling C++ classes (code example 1)" should { - val cpg = code(""" + "handling C++ classes (code example 1)" should { + val cpg = code(""" | class Foo { | member_type x; | }; @@ -18,53 +18,53 @@ class ClassTypeTests extends CCodeToCpgSuite(FileDefaults.CPP_EXT) { | } |""".stripMargin) - "create TYPE node with correct fields for class member" in { - val List(x) = cpg.typ.name("member_type").l - x.fullName shouldBe "member_type" - x.typeDeclFullName shouldBe "member_type" - } - - "create TYPE node with correct fields for return type" in { - val List(x) = cpg.typ.name("ret_type").l - x.fullName shouldBe "ret_type" - x.typeDeclFullName shouldBe "ret_type" - } - - "create TYPE node with correct fields for parameter type" in { - val List(x) = cpg.typ.name("param_type").l - x.fullName shouldBe "param_type" - x.typeDeclFullName shouldBe "param_type" - } - - "create TYPE node with correct fields for local type" in { - val List(x) = cpg.typ.name("local_type").l - x.fullName shouldBe "local_type" - x.typeDeclFullName shouldBe "local_type" - } - - "allow traversing from member's TYPE to member" in { - val List(x) = cpg.typ("member_type").memberOfType.l - x.name shouldBe "x" - } - - "allow traversing from return params TYPE to return param" in { - val List(x) = cpg.typ("ret_type").methodReturnOfType.l - x.typeFullName shouldBe "ret_type" - } - - "allow traversing from params TYPE to param" in { - val List(x) = cpg.typ("param_type").parameterOfType.l - x.name shouldBe "param" - } - - "allow traversing from local's TYPE to local" in { - val List(x) = cpg.typ("local_type").localOfType.l - x.name shouldBe "y" - } - } - - "handling C++ classes (code example 2)" should { - val cpg = code(""" + "create TYPE node with correct fields for class member" in { + val List(x) = cpg.typ.name("member_type").l + x.fullName shouldBe "member_type" + x.typeDeclFullName shouldBe "member_type" + } + + "create TYPE node with correct fields for return type" in { + val List(x) = cpg.typ.name("ret_type").l + x.fullName shouldBe "ret_type" + x.typeDeclFullName shouldBe "ret_type" + } + + "create TYPE node with correct fields for parameter type" in { + val List(x) = cpg.typ.name("param_type").l + x.fullName shouldBe "param_type" + x.typeDeclFullName shouldBe "param_type" + } + + "create TYPE node with correct fields for local type" in { + val List(x) = cpg.typ.name("local_type").l + x.fullName shouldBe "local_type" + x.typeDeclFullName shouldBe "local_type" + } + + "allow traversing from member's TYPE to member" in { + val List(x) = cpg.typ("member_type").memberOfType.l + x.name shouldBe "x" + } + + "allow traversing from return params TYPE to return param" in { + val List(x) = cpg.typ("ret_type").methodReturnOfType.l + x.typeFullName shouldBe "ret_type" + } + + "allow traversing from params TYPE to param" in { + val List(x) = cpg.typ("param_type").parameterOfType.l + x.name shouldBe "param" + } + + "allow traversing from local's TYPE to local" in { + val List(x) = cpg.typ("local_type").localOfType.l + x.name shouldBe "y" + } + } + + "handling C++ classes (code example 2)" should { + val cpg = code(""" | class foo : bar { | char x; | int y; @@ -73,67 +73,75 @@ class ClassTypeTests extends CCodeToCpgSuite(FileDefaults.CPP_EXT) { | | typedef int mytype;""".stripMargin) - "should contain a type decl for `foo` with correct fields" in { - val List(x) = cpg.typeDecl("foo").l - x.fullName shouldBe "foo" - x.isExternal shouldBe false - x.inheritsFromTypeFullName shouldBe List("bar") - x.aliasTypeFullName shouldBe None - x.order shouldBe 1 - x.filename shouldBe "Test0.cpp" - x.filename.endsWith(FileDefaults.CPP_EXT) shouldBe true - } - - "should contain type decl for alias `mytype` of `int`" in { - val List(x) = cpg.typeDecl("mytype").l - x.fullName shouldBe "mytype" - x.isExternal shouldBe false - x.inheritsFromTypeFullName shouldBe List() - x.aliasTypeFullName shouldBe Some("int") - x.code shouldBe "typedef int mytype;" - x.order shouldBe 2 - x.filename shouldBe "Test0.cpp" - x.filename.endsWith(FileDefaults.CPP_EXT) shouldBe true - } - - "should contain type decl for external type `int`" in { - val List(x) = cpg.typeDecl("int").l - x.fullName shouldBe "int" - x.isExternal shouldBe true - x.inheritsFromTypeFullName shouldBe List() - x.aliasTypeFullName shouldBe None - x.order shouldBe -1 - x.filename shouldBe "" - } - - "should find exactly 1 internal type" in { - cpg.typeDecl.nameNot(NamespaceTraversal.globalNamespaceName).internal.name.toSetMutable shouldBe Set("foo") - } - - "should find five external types (`bar`, `char`, `int`, `void`, `ANY`)" in { - cpg.typeDecl.external.name.toSetMutable shouldBe Set("bar", "char", "int", "void", "ANY") - } - - "should find two members for `foo`: `x` and `y`" in { - cpg.typeDecl.name("foo").member.name.toSetMutable shouldBe Set("x", "y") - } - - "should allow traversing from `int` to its alias `mytype`" in { - cpg.typeDecl("int").aliasTypeDecl.name.l shouldBe List("mytype") - cpg.typeDecl("mytype").aliasTypeDecl.l shouldBe List() - } - - "should find one method in type `foo`" in { - cpg.typeDecl.name("foo").method.name.toSetMutable shouldBe Set("method") - } - - "should allow traversing from type to enclosing file" in { - cpg.typeDecl.file.filter(_.name.endsWith(FileDefaults.CPP_EXT)).l should not be empty - } - "handling C++ classes (code example 3)" should { - "generate correct call fullnames" in { - val cpg = code( - """ + "should contain a type decl for `foo` with correct fields" in { + val List(x) = cpg.typeDecl("foo").l + x.fullName shouldBe "foo" + x.isExternal shouldBe false + x.inheritsFromTypeFullName shouldBe List("bar") + x.aliasTypeFullName shouldBe None + x.order shouldBe 1 + x.filename shouldBe "Test0.cpp" + x.filename.endsWith(FileDefaults.CPP_EXT) shouldBe true + } + + "should contain type decl for alias `mytype` of `int`" in { + val List(x) = cpg.typeDecl("mytype").l + x.fullName shouldBe "mytype" + x.isExternal shouldBe false + x.inheritsFromTypeFullName shouldBe List() + x.aliasTypeFullName shouldBe Some("int") + x.code shouldBe "typedef int mytype;" + x.order shouldBe 2 + x.filename shouldBe "Test0.cpp" + x.filename.endsWith(FileDefaults.CPP_EXT) shouldBe true + } + + "should contain type decl for external type `int`" in { + val List(x) = cpg.typeDecl("int").l + x.fullName shouldBe "int" + x.isExternal shouldBe true + x.inheritsFromTypeFullName shouldBe List() + x.aliasTypeFullName shouldBe None + x.order shouldBe -1 + x.filename shouldBe "" + } + + "should find exactly 1 internal type" in { + cpg.typeDecl.nameNot( + NamespaceTraversal.globalNamespaceName + ).internal.name.toSetMutable shouldBe Set("foo") + } + + "should find five external types (`bar`, `char`, `int`, `void`, `ANY`)" in { + cpg.typeDecl.external.name.toSetMutable shouldBe Set( + "bar", + "char", + "int", + "void", + "ANY" + ) + } + + "should find two members for `foo`: `x` and `y`" in { + cpg.typeDecl.name("foo").member.name.toSetMutable shouldBe Set("x", "y") + } + + "should allow traversing from `int` to its alias `mytype`" in { + cpg.typeDecl("int").aliasTypeDecl.name.l shouldBe List("mytype") + cpg.typeDecl("mytype").aliasTypeDecl.l shouldBe List() + } + + "should find one method in type `foo`" in { + cpg.typeDecl.name("foo").method.name.toSetMutable shouldBe Set("method") + } + + "should allow traversing from type to enclosing file" in { + cpg.typeDecl.file.filter(_.name.endsWith(FileDefaults.CPP_EXT)).l should not be empty + } + "handling C++ classes (code example 3)" should { + "generate correct call fullnames" in { + val cpg = code( + """ |class B { |public: | void foo2() {} @@ -153,11 +161,31 @@ class ClassTypeTests extends CCodeToCpgSuite(FileDefaults.CPP_EXT) { | A a; | a.foo1(); | return 0; - |}""".stripMargin) - cpg.call("foo1").methodFullName.toSetMutable shouldBe Set("A.foo1") - cpg.call("foo2").methodFullName.toSetMutable shouldBe Set("B.foo2") - } - } - } - -} + |}""".stripMargin + ) + cpg.call("foo1").methodFullName.toSetMutable shouldBe Set("A.foo1") + cpg.call("foo2").methodFullName.toSetMutable shouldBe Set("B.foo2") + } + } + + "handling C++ class constructors" should { + "generate correct types" in { + val cpg = code( + """ + |class FooT : public Foo { + | public: + | FooT( + | const std::string& a, + | const Bar::SomeClass& b + | ): Bar::Foo(a, b) {} + |}""".stripMargin + ) + val List(constructor) = cpg.typeDecl.nameExact("FooT").method.isConstructor.l + constructor.signature shouldBe "Bar.Foo FooT.FooT (std.string,Bar.SomeClass)" + val List(p1, p2) = constructor.parameter.l + p1.typ.fullName shouldBe "std.string" + p2.typ.fullName shouldBe "Bar.SomeClass" + } + } + } +end ClassTypeTests diff --git a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/NamespaceTypeTests.scala b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/NamespaceTypeTests.scala index a7ba274d..dcb94d0f 100644 --- a/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/NamespaceTypeTests.scala +++ b/platform/frontends/c2cpg/src/test/scala/io/appthreat/c2cpg/passes/types/NamespaceTypeTests.scala @@ -371,35 +371,17 @@ class NamespaceTypeTests extends CCodeToCpgSuite(fileSuffix = FileDefaults.CPP_E finalClasses.name shouldBe "FinalClasses" finalClasses.fullName shouldBe "FinalClasses" } - inside(cpg.typ.name("A").derivedTypeTransitive.l) { case List(b1, c11, c12, b2, c21, c22, c23) => - b1.name shouldBe "B1" - b1.fullName shouldBe "IntermediateClasses.B1" - b1.typeDeclFullName shouldBe "IntermediateClasses.B1" - - c11.name shouldBe "C11" - c11.fullName shouldBe "FinalClasses.C11" - c11.typeDeclFullName shouldBe "FinalClasses.C11" - - c12.name shouldBe "C12" - c12.fullName shouldBe "FinalClasses.C12" - c12.typeDeclFullName shouldBe "FinalClasses.C12" - - b2.name shouldBe "B2" - b2.fullName shouldBe "IntermediateClasses.B2" - b2.typeDeclFullName shouldBe "IntermediateClasses.B2" - - c21.name shouldBe "C21" - c21.fullName shouldBe "FinalClasses.C21" - c21.typeDeclFullName shouldBe "FinalClasses.C21" - - c22.name shouldBe "C22" - c22.fullName shouldBe "FinalClasses.C22" - c22.typeDeclFullName shouldBe "FinalClasses.C22" - - c23.name shouldBe "C23" - c23.fullName shouldBe "FinalClasses.C23" - c23.typeDeclFullName shouldBe "FinalClasses.C23" - } + cpg.typ.name("A").derivedTypeTransitive.typeDeclFullName.sorted.l shouldBe List( + "FinalClasses.C11", + "FinalClasses.C12", + "FinalClasses.C21", + "FinalClasses.C22", + "FinalClasses.C23", + "IntermediateClasses.B1", + "IntermediateClasses.B1*", + "IntermediateClasses.B2", + "IntermediateClasses.B2*" + ) } } diff --git a/platform/frontends/javasrc2cpg/build.sbt b/platform/frontends/javasrc2cpg/build.sbt index 35fbba66..bf83efb5 100644 --- a/platform/frontends/javasrc2cpg/build.sbt +++ b/platform/frontends/javasrc2cpg/build.sbt @@ -4,10 +4,10 @@ dependsOn(Projects.dataflowengineoss, Projects.x2cpg % "compile->compile;test->t libraryDependencies ++= Seq( "io.appthreat" %% "cpg2" % Versions.cpg, - "com.github.javaparser" % "javaparser-symbol-solver-core" % "3.25.10", + "com.github.javaparser" % "javaparser-symbol-solver-core" % "3.26.1", "org.gradle" % "gradle-tooling-api" % Versions.gradleTooling, "org.scalatest" %% "scalatest" % Versions.scalatest % Test, - "org.projectlombok" % "lombok" % "1.18.32", + "org.projectlombok" % "lombok" % "1.18.34", "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4", "org.scala-lang.modules" %% "scala-parser-combinators" % "2.4.0", "net.lingala.zip4j" % "zip4j" % "2.11.5" diff --git a/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/JavaSrc2Cpg.scala b/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/JavaSrc2Cpg.scala index 655bd2a3..e939aeff 100644 --- a/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/JavaSrc2Cpg.scala +++ b/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/JavaSrc2Cpg.scala @@ -14,7 +14,6 @@ import io.appthreat.x2cpg.X2CpgFrontend import io.shiftleft.codepropertygraph.Cpg import io.shiftleft.codepropertygraph.generated.Languages import io.shiftleft.passes.CpgPassBase -import org.slf4j.LoggerFactory import scala.jdk.CollectionConverters.* import scala.util.Try @@ -23,13 +22,12 @@ import scala.util.matching.Regex class JavaSrc2Cpg extends X2CpgFrontend[Config]: import JavaSrc2Cpg.* - private val logger = LoggerFactory.getLogger(this.getClass) - override def createCpg(config: Config): Try[Cpg] = withNewEmptyCpg(config.outputPath, config: Config) { (cpg, config) => new MetaDataPass(cpg, language, config.inputPath).createAndApply() val astCreationPass = new AstCreationPass(config, cpg) astCreationPass.createAndApply() + astCreationPass.clearJavaParserCaches() new ConfigFileCreationPass(cpg).createAndApply() if !config.skipTypeInfPass then TypeNodePass.withRegisteredTypes( @@ -41,7 +39,6 @@ class JavaSrc2Cpg extends X2CpgFrontend[Config]: object JavaSrc2Cpg: val language: String = Languages.JAVASRC - private val logger = LoggerFactory.getLogger(this.getClass) val sourceFileExtensions: Set[String] = Set(".java") diff --git a/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/passes/AstCreationPass.scala b/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/passes/AstCreationPass.scala index 7363c33d..036cf720 100644 --- a/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/passes/AstCreationPass.scala +++ b/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/passes/AstCreationPass.scala @@ -3,6 +3,7 @@ package io.appthreat.javasrc2cpg.passes import better.files.File import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.symbolsolver.JavaSymbolSolver +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade import com.github.javaparser.symbolsolver.resolution.typesolvers.{ JarTypeSolver, ReflectionTypeSolver @@ -26,7 +27,6 @@ class AstCreationPass(config: Config, cpg: Cpg, sourcesOverride: Option[List[Str extends ConcurrentWriterCpgPass[String](cpg): val global: Global = new Global() - private val logger = LoggerFactory.getLogger(classOf[AstCreationPass]) private val sourceFilenames = SourceParser.getSourceFilenames(config, sourcesOverride) @@ -52,7 +52,13 @@ class AstCreationPass(config: Config, cpg: Cpg, sourcesOverride: Option[List[Str ).createAst() ) - case None => logger.debug(s"Skipping AST creation for $filename") + case None => + + /** Clear JavaParser caches. Should only be invoked after we no longer need JavaParser, e.g. as + * soon as we've built the AST layer for all files. + */ + def clearJavaParserCaches(): Unit = + JavaParserFacade.clearInstances() private def initParserAndUtils( config: Config, @@ -69,10 +75,8 @@ class AstCreationPass(config: Config, cpg: Cpg, sourcesOverride: Option[List[Str DependencyResolver.getDependencies(Paths.get(inputPath)) match case Some(deps) => deps.toList case None => - logger.debug(s"Could not fetch dependencies for project at path $inputPath") List() else - logger.debug("dependency resolving disabled") List() private def createSymbolSolver( @@ -91,15 +95,9 @@ class AstCreationPass(config: Config, cpg: Cpg, sourcesOverride: Option[List[Str if javaHome != null && javaHome.nonEmpty then javaHome else System.getenv("JAVA_HOME") case (None, Some(jdkPath)) => - logger.debug( - s"Using JDK path from environment variable ${JavaSrcEnvVar.JdkPath.name} for JDK type information: $jdkPath" - ) jdkPath case (Some(jdkPath), _) => - logger.debug( - s"Using JDK path set with jdk-path option for JDK type information: $jdkPath" - ) jdkPath var jdkJarTypeSolver: JdkJarTypeSolver = null // native-image could have empty JAVA_HOME diff --git a/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/util/SourceParser.scala b/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/util/SourceParser.scala index 1b0b3fd4..a83936c5 100644 --- a/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/util/SourceParser.scala +++ b/platform/frontends/javasrc2cpg/src/main/scala/io/appthreat/javasrc2cpg/util/SourceParser.scala @@ -9,7 +9,6 @@ import com.github.javaparser.ParserConfiguration.LanguageLevel import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.Node.Parsedness import io.appthreat.javasrc2cpg.{Config, JavaSrc2Cpg} -import org.slf4j.LoggerFactory import scala.collection.mutable import scala.jdk.CollectionConverters.* @@ -25,8 +24,6 @@ import scala.util.Success class SourceParser private (originalInputPath: Path, analysisRoot: Path, typesRoot: Path): - private val logger = LoggerFactory.getLogger(this.getClass) - /** Parse the given file into a JavaParser CompliationUnit that will be used for creating the * CPG AST. * @@ -70,24 +67,13 @@ class SourceParser private (originalInputPath: Path, analysisRoot: Path, typesRo .setStoreTokens(storeTokens) val parseResult = new JavaParser(javaParserConfig).parse(file.toJava) - parseResult.getProblems.asScala.toList match - case Nil => // Just carry on as usual - case problems => - logger.debug(s"Encountered problems while parsing file ${file.name}:") - problems.foreach { problem => - logger.debug(s"- ${problem.getMessage}") - } - parseResult.getResult.toScala match case Some(result) if result.getParsed == Parsedness.PARSED => Some(result) case _ => - logger.debug(s"Failed to parse file ${file.name}") None - end parse end SourceParser object SourceParser: - private val logger = LoggerFactory.getLogger(this.getClass) def apply(config: Config, hasLombokDependency: Boolean): SourceParser = val canonicalInputPath = File(config.inputPath).canonicalPath @@ -128,7 +114,6 @@ object SourceParser: if delombokDir.nonEmpty then Delombok.parseDelombokModeOption(delombokMode) match case Default if hasLombokDependency => - logger.debug(s"Analysing delomboked code as lombok dependency was found.") (delombokDir, delombokDir) case Default => (originalDir, originalDir) diff --git a/platform/frontends/jssrc2cpg/build.sbt b/platform/frontends/jssrc2cpg/build.sbt index 929934e6..62e3b675 100644 --- a/platform/frontends/jssrc2cpg/build.sbt +++ b/platform/frontends/jssrc2cpg/build.sbt @@ -20,7 +20,7 @@ astGenVersion := appProperties.value.getString("jssrc2cpg.astgen_version") libraryDependencies ++= Seq( "io.appthreat" %% "cpg2" % Versions.cpg, "com.lihaoyi" %% "upickle" % Versions.upickle, - "com.fasterxml.jackson.core" % "jackson-databind" % "2.17.0", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.17.1", "com.typesafe" % "config" % "1.4.3", "com.michaelpollmeier" % "versionsort" % "1.0.11", "org.scalatest" %% "scalatest" % Versions.scalatest % Test diff --git a/platform/frontends/jssrc2cpg/src/main/scala/io/appthreat/jssrc2cpg/passes/JavaScriptTypeRecovery.scala b/platform/frontends/jssrc2cpg/src/main/scala/io/appthreat/jssrc2cpg/passes/JavaScriptTypeRecovery.scala index 8e16e4cf..a1d84109 100644 --- a/platform/frontends/jssrc2cpg/src/main/scala/io/appthreat/jssrc2cpg/passes/JavaScriptTypeRecovery.scala +++ b/platform/frontends/jssrc2cpg/src/main/scala/io/appthreat/jssrc2cpg/passes/JavaScriptTypeRecovery.scala @@ -36,6 +36,15 @@ private class RecoverForJavaScriptFile( state: XTypeRecoveryState ) extends RecoverForXCompilationUnit[File](cpg, cu, builder, state): + private lazy val exportedIdentifiers = cu.method + .nameExact(":program") + .flatMap(_._callViaContainsOut) + .nameExact(Operators.assignment) + .filter(_.code.startsWith("exports.*")) + .argument + .isIdentifier + .name + .toSet override protected val pathSep = ':' /** A heuristic method to determine if a call is a constructor or not. @@ -103,16 +112,6 @@ private class RecoverForJavaScriptFile( } end prepopulateSymbolTable - private lazy val exportedIdentifiers = cu.method - .nameExact(":program") - .flatMap(_._callViaContainsOut) - .nameExact(Operators.assignment) - .filter(_.code.startsWith("exports.*")) - .argument - .isIdentifier - .name - .toSet - override protected def isField(i: Identifier): Boolean = state.isFieldCache.getOrElseUpdate( i.id(), @@ -124,12 +123,15 @@ private class RecoverForJavaScriptFile( c: Call ): Set[String] = val constructorPaths = if c.methodFullName.endsWith(".alloc") then - def newChildren = - c.inAssignment.astSiblings.isCall.nameExact(".new").astChildren + val newOp = c.inAssignment.astSiblings.isCall.nameExact(".new").headOption + val newChildren = newOp.astChildren.l val possibleImportIdentifier = newChildren.isIdentifier.headOption match case Some(i) if GlobalBuiltins.builtins.contains(i.name) => Set(s"__ecma.${i.name}") - case Some(i) => symbolTable.get(i) - case None => Set.empty[String] + case Some(i) => + val typs = symbolTable.get(CallAlias(i.name, Option("this"))) + if typs.nonEmpty then newOp.foreach(symbolTable.put(_, typs)) + symbolTable.get(i) + case None => Set.empty[String] lazy val possibleConstructorPointer = newChildren.astChildren.isFieldIdentifier.map(f => CallAlias(f.canonicalName, Some("this")) diff --git a/platform/frontends/jssrc2cpg/src/test/scala/io/appthreat/jssrc2cpg/passes/TypeRecoveryPassTests.scala b/platform/frontends/jssrc2cpg/src/test/scala/io/appthreat/jssrc2cpg/passes/TypeRecoveryPassTests.scala index c01d3776..99609d10 100644 --- a/platform/frontends/jssrc2cpg/src/test/scala/io/appthreat/jssrc2cpg/passes/TypeRecoveryPassTests.scala +++ b/platform/frontends/jssrc2cpg/src/test/scala/io/appthreat/jssrc2cpg/passes/TypeRecoveryPassTests.scala @@ -3,12 +3,11 @@ package io.appthreat.jssrc2cpg.passes import io.appthreat.jssrc2cpg.testfixtures.DataFlowCodeToCpgSuite import io.appthreat.x2cpg.passes.frontend.ImportsPass.* import io.shiftleft.semanticcpg.language.* -import io.shiftleft.semanticcpg.language.NoResolve -class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { +class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite: - "literals declared from built-in types" should { - val cpg = code(""" + "literals declared from built-in types" should { + val cpg = code(""" |let x = 123; | |function foo_shadowing() { @@ -21,20 +20,20 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { |z.push(4) |""".stripMargin) - "resolve 'z' types correctly" in { - // The dictionary/object type is just considered "ANY" which is fine for now - cpg.identifier("z").typeFullName.toSet.headOption shouldBe Some("__ecma.Array") - } + "resolve 'z' types correctly" in { + // The dictionary/object type is just considered "ANY" which is fine for now + cpg.identifier("z").typeFullName.toSet.headOption shouldBe Some("__ecma.Array") + } - "resolve 'z' identifier call correctly" in { - val List(zAppend) = cpg.call("push").l - zAppend.methodFullName shouldBe "__ecma.Array:push" + "resolve 'z' identifier call correctly" in { + val List(zAppend) = cpg.call("push").l + zAppend.methodFullName shouldBe "__ecma.Array:push" + } } - } - "call from a function from an external type" should { - val cpg = code( - """ + "call from a function from an external type" should { + val cpg = code( + """ |import { WebClient } from "slack_sdk"; |import { SendGridAPIClient } from "sendgrid"; | @@ -47,79 +46,79 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { | |let response = sg.send(message); |""".stripMargin, - "Test1.ts" - ) - - "resolve correct imports via tag nodes" in { - val List(a: UnknownMethod, b: UnknownTypeDecl, x: UnknownMethod, y: UnknownTypeDecl) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - a.fullName shouldBe "slack_sdk:WebClient" - b.fullName shouldBe "slack_sdk:WebClient" - x.fullName shouldBe "sendgrid:SendGridAPIClient" - y.fullName shouldBe "sendgrid:SendGridAPIClient" - } + "Test1.ts" + ) - "resolve 'sg' identifier types from import information" in { - val List(sg1, sg2, sg3) = cpg.identifier.nameExact("sg").l - sg1.typeFullName shouldBe "sendgrid:SendGridAPIClient" - sg2.typeFullName shouldBe "sendgrid:SendGridAPIClient" - sg3.typeFullName shouldBe "sendgrid:SendGridAPIClient" - } + "resolve correct imports via tag nodes" in { + val List(a: UnknownMethod, b: UnknownTypeDecl, x: UnknownMethod, y: UnknownTypeDecl) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + a.fullName shouldBe "slack_sdk:WebClient" + b.fullName shouldBe "slack_sdk:WebClient" + x.fullName shouldBe "sendgrid:SendGridAPIClient" + y.fullName shouldBe "sendgrid:SendGridAPIClient" + } - "resolve 'sg' call path from import information" in { - val List(sendCall) = cpg.call("send").l - sendCall.methodFullName shouldBe "sendgrid:SendGridAPIClient:send" - } + "resolve 'sg' identifier types from import information" in { + val List(sg1, sg2, sg3) = cpg.identifier.nameExact("sg").l + sg1.typeFullName shouldBe "sendgrid:SendGridAPIClient" + sg2.typeFullName shouldBe "sendgrid:SendGridAPIClient" + sg3.typeFullName shouldBe "sendgrid:SendGridAPIClient" + } - "resolve 'client' identifier types from import information" in { - val List(client1, client2, client3) = cpg.identifier.nameExact("client").l - client1.typeFullName shouldBe "slack_sdk:WebClient" - client2.typeFullName shouldBe "slack_sdk:WebClient" - client3.typeFullName shouldBe "slack_sdk:WebClient" - } + "resolve 'sg' call path from import information" in { + val List(sendCall) = cpg.call("send").l + sendCall.methodFullName shouldBe "sendgrid:SendGridAPIClient:send" + } - "resolve 'client' call path from identifier in child scope" in { - val List(postMessage) = cpg.call("chatPostMessage").l - postMessage.methodFullName shouldBe "slack_sdk:WebClient:chatPostMessage" - } + "resolve 'client' identifier types from import information" in { + val List(client1, client2, client3) = cpg.identifier.nameExact("client").l + client1.typeFullName shouldBe "slack_sdk:WebClient" + client2.typeFullName shouldBe "slack_sdk:WebClient" + client3.typeFullName shouldBe "slack_sdk:WebClient" + } - "resolve a dummy 'send' return value from sg.send" in { - val List(postMessage) = cpg.identifier("response").l - postMessage.typeFullName shouldBe "sendgrid:SendGridAPIClient:send:" - } + "resolve 'client' call path from identifier in child scope" in { + val List(postMessage) = cpg.call("chatPostMessage").l + postMessage.methodFullName shouldBe "slack_sdk:WebClient:chatPostMessage" + } - } + "resolve a dummy 'send' return value from sg.send" in { + val List(postMessage) = cpg.identifier("response").l + postMessage.typeFullName shouldBe "sendgrid:SendGridAPIClient:send:" + } + + } - "recovering paths for built-in calls" should { - val cpg = code(""" + "recovering paths for built-in calls" should { + val cpg = code(""" |console.log("Hello world"); |let x = Math.abs(-1); |""".stripMargin) - "resolve 'print' and 'max' calls" in { - val List(printCall) = cpg.call("log").l - printCall.methodFullName shouldBe "__whatwg.console:log" - val List(maxCall) = cpg.call("abs").l - maxCall.methodFullName shouldBe "__ecma.Math:abs" - val List(x) = cpg.identifier("x").l - // TODO: Ideally we would know the result of `abs` but this can be a future task - x.typeFullName shouldBe "__ecma.Math:abs:" - } + "resolve 'print' and 'max' calls" in { + val List(printCall) = cpg.call("log").l + printCall.methodFullName shouldBe "__whatwg.console:log" + val List(maxCall) = cpg.call("abs").l + maxCall.methodFullName shouldBe "__ecma.Math:abs" + val List(x) = cpg.identifier("x").l + // TODO: Ideally we would know the result of `abs` but this can be a future task + x.typeFullName shouldBe "__ecma.Math:abs:" + } - } + } - "recovering module members across modules" should { - val cpg = code( - """ + "recovering module members across modules" should { + val cpg = code( + """ |import { SQLAlchemy } from "flask_sqlalchemy"; | |export const x = 1; |export const y = "test"; |export const db = new SQLAlchemy(); |""".stripMargin, - "Foo.ts" - ).moreCode( - """ + "Foo.ts" + ).moreCode( + """ |import { x, y, db } from './Foo'; | |let z = x; @@ -131,74 +130,86 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { | |db.deleteTable(); |""".stripMargin, - "Bar.ts" - ) - - "resolve correct imports via tag nodes" in { - val List(a: ResolvedMember, b: ResolvedMember, c: ResolvedMember, d: UnknownMethod, e: UnknownTypeDecl) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - a.basePath shouldBe "Foo.ts::program" - a.memberName shouldBe "x" - b.basePath shouldBe "Foo.ts::program" - b.memberName shouldBe "y" - c.basePath shouldBe "Foo.ts::program" - c.memberName shouldBe "db" - d.fullName shouldBe "flask_sqlalchemy:SQLAlchemy" - e.fullName shouldBe "flask_sqlalchemy:SQLAlchemy" - } + "Bar.ts" + ) - "resolve 'x' and 'y' locally under Foo.ts" in { - val List(x1, x2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("x").l - x1.typeFullName shouldBe "__ecma.Number" - x2.typeFullName shouldBe "__ecma.Number" - val List(y1, y2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("y").l - y1.typeFullName shouldBe "__ecma.String" - y2.typeFullName shouldBe "__ecma.String" - val List(db1, db2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("db").l - db1.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" - db2.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" - } + "resolve correct imports via tag nodes" in { + val List( + a: ResolvedMember, + b: ResolvedMember, + c: ResolvedMember, + d: UnknownMethod, + e: UnknownTypeDecl + ) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + a.basePath shouldBe "Foo.ts::program" + a.memberName shouldBe "x" + b.basePath shouldBe "Foo.ts::program" + b.memberName shouldBe "y" + c.basePath shouldBe "Foo.ts::program" + c.memberName shouldBe "db" + d.fullName shouldBe "flask_sqlalchemy:SQLAlchemy" + e.fullName shouldBe "flask_sqlalchemy:SQLAlchemy" + } - "resolve 'foo.x' and 'foo.y' field access primitive types correctly" in { - val List(z1, z2) = cpg.file.name(".*Bar.*").ast.isIdentifier.nameExact("z").l - z1.typeFullName shouldBe "__ecma.String" - z1.dynamicTypeHintFullName shouldBe Seq("__ecma.Number") - z2.typeFullName shouldBe "__ecma.String" - z2.dynamicTypeHintFullName shouldBe Seq("__ecma.Number") - } + "resolve 'x' and 'y' locally under Foo.ts" in { + val List(x1, x2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("x").l + x1.typeFullName shouldBe "__ecma.Number" + x2.typeFullName shouldBe "__ecma.Number" + val List(y1, y2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("y").l + y1.typeFullName shouldBe "__ecma.String" + y2.typeFullName shouldBe "__ecma.String" + val List(db1, db2) = cpg.file.name(".*Foo.*").ast.isIdentifier.nameExact("db").l + db1.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" + db2.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" + } - "resolve 'foo.d' field access object types correctly" in { - val List(d1, d2, d3) = cpg.file.name(".*Bar.*").ast.isIdentifier.nameExact("d").l - d1.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" - d1.dynamicTypeHintFullName shouldBe Seq() - d2.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" - d2.dynamicTypeHintFullName shouldBe Seq() - d3.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" - d3.dynamicTypeHintFullName shouldBe Seq() - } + "resolve 'foo.x' and 'foo.y' field access primitive types correctly" in { + val List(z1, z2) = cpg.file.name(".*Bar.*").ast.isIdentifier.nameExact("z").l + z1.typeFullName shouldBe "__ecma.String" + z1.dynamicTypeHintFullName shouldBe Seq("__ecma.Number") + z2.typeFullName shouldBe "__ecma.String" + z2.dynamicTypeHintFullName shouldBe Seq("__ecma.Number") + } - "resolve a 'createTable' call indirectly from 'foo.d' field access correctly" in { - val List(d) = cpg.file.name(".*Bar.*").ast.isCall.name("createTable").l - d.dynamicTypeHintFullName shouldBe Seq("d.createTable", "flask_sqlalchemy:SQLAlchemy:createTable") - d.callee(NoResolve).isExternal.headOption shouldBe Some(true) - } + "resolve 'foo.d' field access object types correctly" in { + val List(d1, d2, d3) = cpg.file.name(".*Bar.*").ast.isIdentifier.nameExact("d").l + d1.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" + d1.dynamicTypeHintFullName shouldBe Seq() + d2.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" + d2.dynamicTypeHintFullName shouldBe Seq() + d3.typeFullName shouldBe "flask_sqlalchemy:SQLAlchemy" + d3.dynamicTypeHintFullName shouldBe Seq() + } - "resolve a 'deleteTable' call directly from 'foo.db' field access correctly" in { - val List(d) = cpg.file - .name(".*Bar.*") - .ast - .isCall - .name("deleteTable") - .l - d.dynamicTypeHintFullName shouldBe Seq("db.deleteTable", "flask_sqlalchemy:SQLAlchemy:deleteTable") - d.callee(NoResolve).isExternal.headOption shouldBe Some(true) - } + "resolve a 'createTable' call indirectly from 'foo.d' field access correctly" in { + val List(d) = cpg.file.name(".*Bar.*").ast.isCall.name("createTable").l + d.dynamicTypeHintFullName shouldBe Seq( + "d.createTable", + "flask_sqlalchemy:SQLAlchemy:createTable" + ) + d.callee(NoResolve).isExternal.headOption shouldBe Some(true) + } - } + "resolve a 'deleteTable' call directly from 'foo.db' field access correctly" in { + val List(d) = cpg.file + .name(".*Bar.*") + .ast + .isCall + .name("deleteTable") + .l + d.dynamicTypeHintFullName shouldBe Seq( + "db.deleteTable", + "flask_sqlalchemy:SQLAlchemy:deleteTable" + ) + d.callee(NoResolve).isExternal.headOption shouldBe Some(true) + } + + } - "Importing an anonymous function" should { - val cpg = code( - """ + "Importing an anonymous function" should { + val cpg = code( + """ |var refThis = this; | |exports.getIncrementalInteger = (function() { @@ -211,79 +222,80 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { | |refThis.getIncrementalInteger(); |""".stripMargin, - "util.js" - ).moreCode( - """ + "util.js" + ).moreCode( + """ |var util = require("./util.js"); | |util.getIncrementalInteger() |""".stripMargin, - "foo.js" - ) + "foo.js" + ) - "resolve correct imports via tag nodes" in { - val List(x: ResolvedMethod) = cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - x.fullName shouldBe "util.js::program:getIncrementalInteger" - } + "resolve correct imports via tag nodes" in { + val List(x: ResolvedMethod) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + x.fullName shouldBe "util.js::program:getIncrementalInteger" + } - "resolve the method full name off of an aliased 'this'" in { - val List(x) = cpg.file("util.js").ast.isCall.nameExact("getIncrementalInteger").l - x.methodFullName shouldBe "util.js::program:getIncrementalInteger" - } + "resolve the method full name off of an aliased 'this'" in { + val List(x) = cpg.file("util.js").ast.isCall.nameExact("getIncrementalInteger").l + x.methodFullName shouldBe "util.js::program:getIncrementalInteger" + } - "resolve the full name of the currying from the closure" in { - val List(x) = cpg.file("util.js").ast.isCall.nameExact("anonymous").lineNumber(4).l - x.name shouldBe "anonymous" - x.methodFullName shouldBe "util.js::program:anonymous" + "resolve the full name of the currying from the closure" in { + val List(x) = cpg.file("util.js").ast.isCall.nameExact("anonymous").lineNumber(4).l + x.name shouldBe "anonymous" + x.methodFullName shouldBe "util.js::program:anonymous" + } } - } - "Type obtained via assignment from `require`" should { - val cpg = code(""" + "Type obtained via assignment from `require`" should { + val cpg = code(""" |const google = require('googleapis'); |const driveObj = google.drive({ version: 'v3', auth }); |""".stripMargin) - "resolve correct imports via tag nodes" in { - val List(x: UnknownMethod, y: UnknownTypeDecl) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - x.fullName shouldBe "googleapis" - y.fullName shouldBe "googleapis" - } + "resolve correct imports via tag nodes" in { + val List(x: UnknownMethod, y: UnknownTypeDecl) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + x.fullName shouldBe "googleapis" + y.fullName shouldBe "googleapis" + } - "be propagated to `methodFullName` of call" in { - val List(methodFullName) = cpg.call.code("google.drive\\(.*").methodFullName.l - methodFullName shouldBe "googleapis:drive" - val List(typeFullName) = cpg.identifier.name("driveObj").typeFullName.l - typeFullName shouldBe "googleapis:drive:" - } + "be propagated to `methodFullName` of call" in { + val List(methodFullName) = cpg.call.code("google.drive\\(.*").methodFullName.l + methodFullName shouldBe "googleapis:drive" + val List(typeFullName) = cpg.identifier.name("driveObj").typeFullName.l + typeFullName shouldBe "googleapis:drive:" + } - } + } - "Type obtained via assignment from `require` to {...}" should { - val cpg = code(""" + "Type obtained via assignment from `require` to {...}" should { + val cpg = code(""" |const { google } = require('googleapis'); |const driveObj = google.drive({ version: 'v3', auth }); |""".stripMargin) - "resolve correct imports via tag nodes" in { - val List(x: UnknownMethod, y: UnknownTypeDecl, z: UnknownMethod) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - x.fullName shouldBe "googleapis" - y.fullName shouldBe "googleapis" - z.fullName shouldBe "googleapis" - } + "resolve correct imports via tag nodes" in { + val List(x: UnknownMethod, y: UnknownTypeDecl, z: UnknownMethod) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + x.fullName shouldBe "googleapis" + y.fullName shouldBe "googleapis" + z.fullName shouldBe "googleapis" + } - "be propagated to `methodFullName` of call" in { - val List(methodFullName) = cpg.call.code("google.drive\\(.*").methodFullName.l - methodFullName shouldBe "googleapis:drive" - val List(typeFullName) = cpg.identifier.name("driveObj").typeFullName.l - typeFullName shouldBe "googleapis:drive:" + "be propagated to `methodFullName` of call" in { + val List(methodFullName) = cpg.call.code("google.drive\\(.*").methodFullName.l + methodFullName shouldBe "googleapis:drive" + val List(typeFullName) = cpg.identifier.name("driveObj").typeFullName.l + typeFullName shouldBe "googleapis:drive:" + } } - } - "Type obtained via field access from 'require' derived identifier" should { - val cpg = code(""" + "Type obtained via field access from 'require' derived identifier" should { + val cpg = code(""" |import google from 'googleapis'; |export const authObj = new google.auth.GoogleAuth({ | keyFile: 'path/to/your/credentials.json', @@ -291,35 +303,35 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { |}); |""".stripMargin) - "be propagated to `methodFullName` of call" in { - val List(constructor) = cpg.call.code("new google.auth.GoogleAuth\\(.*").l - constructor.methodFullName shouldBe "googleapis:google:(auth):GoogleAuth:" - val List(authObj1, authObj2) = cpg.identifier.name("authObj").l - authObj1.typeFullName shouldBe "googleapis:google:(auth):GoogleAuth" - authObj2.typeFullName shouldBe "googleapis:google:(auth):GoogleAuth" + "be propagated to `methodFullName` of call" in { + val List(constructor) = cpg.call.code("new google.auth.GoogleAuth\\(.*").l + constructor.methodFullName shouldBe "googleapis:google:(auth):GoogleAuth:" + val List(authObj1, authObj2) = cpg.identifier.name("authObj").l + authObj1.typeFullName shouldBe "googleapis:google:(auth):GoogleAuth" + authObj2.typeFullName shouldBe "googleapis:google:(auth):GoogleAuth" + } } - } - "Type casts of an identifier and call receiver" should { - val cpg = code(""" + "Type casts of an identifier and call receiver" should { + val cpg = code(""" |let imgScr: string = this.imageElement; |this.imageElement = new HTMLImageElement(); |(this.imageElement).src = imgScr; |""".stripMargin) - "succeed in propagating type cast identifiers" in { - val List(imgSrc1, imgSrc2) = cpg.identifier("imgScr").l - imgSrc1.typeFullName shouldBe "__ecma.String" - imgSrc2.typeFullName shouldBe "__ecma.String" - val List(tmp1, tmp2, tmp3) = cpg.identifier("_tmp_0").l - tmp1.typeFullName shouldBe "__ecma.HTMLImageElement" - tmp2.typeFullName shouldBe "__ecma.HTMLImageElement" - tmp3.typeFullName shouldBe "__ecma.HTMLImageElement" + "succeed in propagating type cast identifiers" in { + val List(imgSrc1, imgSrc2) = cpg.identifier("imgScr").l + imgSrc1.typeFullName shouldBe "__ecma.String" + imgSrc2.typeFullName shouldBe "__ecma.String" + val List(tmp1, tmp2, tmp3) = cpg.identifier("_tmp_0").l + tmp1.typeFullName shouldBe "__ecma.HTMLImageElement" + tmp2.typeFullName shouldBe "__ecma.HTMLImageElement" + tmp3.typeFullName shouldBe "__ecma.HTMLImageElement" + } } - } - "Type hints for method parameters and returns" should { - val cpg = code(""" + "Type hints for method parameters and returns" should { + val cpg = code(""" |import google from 'googleapis'; | |function foo(a: google.More, b: google.Money): google.Problems { @@ -328,19 +340,19 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { |} |""".stripMargin) - "be propagated within the method full name reflecting the import `googleapis`" in { - val List(bar) = cpg.call("bar").l - bar.methodFullName shouldBe "googleapis:google:More:bar" - val List(baz) = cpg.call("baz").l - baz.methodFullName shouldBe "googleapis:google:Money:baz" - val List(foo) = cpg.method("foo").methodReturn.l - foo.typeFullName shouldBe "googleapis:google:Problems" + "be propagated within the method full name reflecting the import `googleapis`" in { + val List(bar) = cpg.call("bar").l + bar.methodFullName shouldBe "googleapis:google:More:bar" + val List(baz) = cpg.call("baz").l + baz.methodFullName shouldBe "googleapis:google:Money:baz" + val List(foo) = cpg.method("foo").methodReturn.l + foo.typeFullName shouldBe "googleapis:google:Problems" + } } - } - "Recovered values that are returned in methods" should { - val cpg = code( - """ + "Recovered values that are returned in methods" should { + val cpg = code( + """ |const axios = require("axios"); | |exports.literalFunction = function() { return 2; }; @@ -356,50 +368,56 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { |}; | |""".stripMargin, - "foo.js" - ).moreCode( - """ + "foo.js" + ).moreCode( + """ |const foo = require("./foo"); | |const x = foo.literalFunction(); |const y = foo.get(); |""".stripMargin, - "bar.js" - ) - - "resolve correct imports via tag nodes" in { - val List(a: ResolvedTypeDecl, b: ResolvedMethod, c: ResolvedMethod, d: UnknownMethod, e: UnknownTypeDecl) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - a.fullName shouldBe "foo.js::program" - b.fullName shouldBe "foo.js::program:literalFunction" - c.fullName shouldBe "foo.js::program:get" - d.fullName shouldBe "axios" - e.fullName shouldBe "axios" + "bar.js" + ) - } + "resolve correct imports via tag nodes" in { + val List( + a: ResolvedTypeDecl, + b: ResolvedMethod, + c: ResolvedMethod, + d: UnknownMethod, + e: UnknownTypeDecl + ) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + a.fullName shouldBe "foo.js::program" + b.fullName shouldBe "foo.js::program:literalFunction" + c.fullName shouldBe "foo.js::program:get" + d.fullName shouldBe "axios" + e.fullName shouldBe "axios" - "propagate literal types to the method return" in { - val List(literalMethod) = cpg.method.nameExact("literalFunction").l - literalMethod.methodReturn.typeFullName shouldBe "__ecma.Number" - val List(x) = cpg.identifier("x").l - x.typeFullName shouldBe "__ecma.Number" - val List(literalCall) = cpg.call.nameExact("literalFunction").l - literalCall.typeFullName shouldBe "__ecma.Number" - } + } - "propagate complex types to the method return" in { - val List(getMethod) = cpg.method.nameExact("get").lineNumber(12).l - getMethod.methodReturn.typeFullName shouldBe "axios:create::get:" - val List(y) = cpg.identifier("y").l - y.typeFullName shouldBe "axios:create::get:" - val List(getCall) = cpg.call.nameExact("get").lineNumber(5).l - getCall.typeFullName shouldBe "axios:create::get:" + "propagate literal types to the method return" in { + val List(literalMethod) = cpg.method.nameExact("literalFunction").l + literalMethod.methodReturn.typeFullName shouldBe "__ecma.Number" + val List(x) = cpg.identifier("x").l + x.typeFullName shouldBe "__ecma.Number" + val List(literalCall) = cpg.call.nameExact("literalFunction").l + literalCall.typeFullName shouldBe "__ecma.Number" + } + + "propagate complex types to the method return" in { + val List(getMethod) = cpg.method.nameExact("get").lineNumber(12).l + getMethod.methodReturn.typeFullName shouldBe "axios:create::get:" + val List(y) = cpg.identifier("y").l + y.typeFullName shouldBe "axios:create::get:" + val List(getCall) = cpg.call.nameExact("get").lineNumber(5).l + getCall.typeFullName shouldBe "axios:create::get:" + } } - } - "Temporary variables inserted to produce a three-address code structure" should { - val cpg = code( - """ + "Temporary variables inserted to produce a three-address code structure" should { + val cpg = code( + """ |import { HttpClient } from '@angular/common/http'; | |@Injectable({ @@ -412,20 +430,24 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { | } |} |""".stripMargin, - "foo.ts" - ) + "foo.ts" + ) - "have their calls from a field access structure successfully recovered" in { - cpg.identifier("_tmp_2").typeFullName.headOption shouldBe Some("@angular/common/http:HttpClient") - cpg.call("post").methodFullName.headOption shouldBe Some("@angular/common/http:HttpClient:post") - } + "have their calls from a field access structure successfully recovered" in { + cpg.identifier("_tmp_2").typeFullName.headOption shouldBe Some( + "@angular/common/http:HttpClient" + ) + cpg.call("post").methodFullName.headOption shouldBe Some( + "@angular/common/http:HttpClient:post" + ) + } - } + } - "Members initialized from constructors where the parameter has a type hint" should { + "Members initialized from constructors where the parameter has a type hint" should { - val cpg = code( - """ + val cpg = code( + """ |import { HttpClient } from '@angular/common/http'; | |@Injectable({ @@ -440,21 +462,63 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { | } |} |""".stripMargin, - "foo.ts" - ) + "foo.ts" + ) + + "have the type hint recovered and successfully propagated" in { + val m = cpg.method.fullNameExact("foo.ts::program:SharedService:").head + m.parameter.nameExact("http").typeFullName.headOption shouldBe Some( + "@angular/common/http:HttpClient" + ) + cpg.call("post").methodFullName.headOption shouldBe Some( + "@angular/common/http:HttpClient:post" + ) + } + + } + + "resolve a function full name called as a constructor" in { + val cpg = code( + """ + |var Print = function(str) { + | console.log(str); + |} + | + |new Print("Hello") + |""".stripMargin + ) - "have the type hint recovered and successfully propagated" in { - val m = cpg.method.fullNameExact("foo.ts::program:SharedService:").head - m.parameter.nameExact("http").typeFullName.headOption shouldBe Some("@angular/common/http:HttpClient") - cpg.call("post").methodFullName.headOption shouldBe Some("@angular/common/http:HttpClient:post") + cpg.call.nameExact(".new").methodFullName.head shouldBe "Test0.js::program:Print" } - } + "A function assigned to a member should have it's full name resolved" in { + val cpg = code( + """ + |var foo = {}; + | + |foo.bar = {}; + | + |foo.bar.evaluator = function evaluator (src) { + | eval(src); + |}; + | + |foo.bar.getGlobals = function getGlobals (src) { + | "use strict"; + | var original = Object.keys(global); + | foo.bar.evaluator(src); + |}; + |""".stripMargin + ) + + cpg.call( + "evaluator" + ).methodFullName.head shouldBe "foo:bar::(bar):evaluator" + } "Default exports with calls" should { val cpg = code( - """ + """ |const logger = require('./logger'); | |function a(){ @@ -463,13 +527,14 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { | |a(); |""".stripMargin, - "app.js" + "app.js" ).moreCode( """ |const pino = require('pino'); | |module.exports = pino({}); - |""".stripMargin, "logger.js" + |""".stripMargin, + "logger.js" ) "have the correct method full name" in { @@ -477,5 +542,4 @@ class TypeRecoveryPassTests extends DataFlowCodeToCpgSuite { } } - -} +end TypeRecoveryPassTests diff --git a/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonAstVisitor.scala b/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonAstVisitor.scala index f39a8d34..8e62fb05 100644 --- a/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonAstVisitor.scala +++ b/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonAstVisitor.scala @@ -12,7 +12,7 @@ import io.appthreat.pythonparser.ast import io.appthreat.pysrc2cpg.memop.* import io.appthreat.x2cpg.ValidationMode import io.shiftleft.codepropertygraph.generated.* -import io.shiftleft.codepropertygraph.generated.nodes.{NewNode, NewTypeDecl} +import io.shiftleft.codepropertygraph.generated.nodes.{NewCall, NewIdentifier, NewNode, NewTypeDecl} import overflowdb.BatchedUpdate.DiffGraphBuilder import scala.collection.mutable @@ -243,7 +243,7 @@ class PythonAstVisitor( * The lowering is: * func = f1(arg)(f2(func)) * - * This function takes a method ref, wrappes it in the decorator calls and returns the resulting expression. + * This function takes a method ref, wraps it in the decorator calls and returns the resulting expression. * In the example case this is: * f1(arg)(f2(func)) */ @@ -253,9 +253,13 @@ class PythonAstVisitor( ): nodes.NewNode = decoratorList.foldRight(methodRefNode)( (decorator: ast.iexpr, wrappedMethodRef: nodes.NewNode) => + val (decoratorNode, decoratorName) = convert(decorator) match + case decoratorNode: NewIdentifier => decoratorNode -> decoratorNode.name + case decoratorNode => + decoratorNode -> "" // other decorators are dynamic so we leave this blank createCall( - convert(decorator), - "", + decoratorNode, + decoratorName, lineAndColOf(decorator), wrappedMethodRef :: Nil, Nil diff --git a/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonTypeRecovery.scala b/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonTypeRecovery.scala index 17ab2a3f..187aff15 100644 --- a/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonTypeRecovery.scala +++ b/platform/frontends/pysrc2cpg/src/main/scala/io/appthreat/pysrc2cpg/PythonTypeRecovery.scala @@ -6,7 +6,7 @@ import io.shiftleft.codepropertygraph.generated.nodes.* import io.shiftleft.codepropertygraph.generated.{Operators, PropertyNames} import io.shiftleft.semanticcpg.language.* import io.shiftleft.semanticcpg.language.operatorextension.OpNodes -import OpNodes.FieldAccess +import io.shiftleft.semanticcpg.language.operatorextension.OpNodes.FieldAccess import overflowdb.BatchedUpdate.DiffGraphBuilder class PythonTypeRecoveryPass(cpg: Cpg, config: XTypeRecoveryConfig = XTypeRecoveryConfig()) @@ -38,14 +38,6 @@ private class RecoverForPythonFile( state: XTypeRecoveryState ) extends RecoverForXCompilationUnit[File](cpg, cu, builder, state): - /** Replaces the `this` prefix with the Pythonic `self` prefix for instance methods of functions - * local to this compilation unit. - */ - private def fromNodeToLocalPythonKey(node: AstNode): Option[LocalKey] = - node match - case n: Method => Option(CallAlias(n.name, Option("self"))) - case _ => SBKey.fromNodeToLocalKey(node) - override val symbolTable: SymbolTable[LocalKey] = new SymbolTable[LocalKey](fromNodeToLocalPythonKey) @@ -92,9 +84,6 @@ private class RecoverForPythonFile( override def isConstructor(c: Call): Boolean = isConstructor(c.name) && c.code.endsWith(")") - override protected def isConstructor(name: String): Boolean = - name.nonEmpty && name.charAt(0).isUpper - /** If the parent method is module then it can be used as a field. */ override def isField(i: Identifier): Boolean = @@ -119,6 +108,9 @@ private class RecoverForPythonFile( associateTypes(i, Set(s"${PythonAstVisitor.builtinPrefix}set")) case Operators.conditional => associateTypes(i, Set(s"${PythonAstVisitor.builtinPrefix}bool")) + case Operators.indexAccess => + c.argument.argumentIndex(1).isCall.foreach(setCallMethodFullNameFromBase) + visitIdentifierAssignedToIndexAccess(i, c) case _ => super.visitIdentifierAssignedToOperator(i, c, operation) override def visitIdentifierAssignedToConstructor(i: Identifier, c: Call): Set[String] = @@ -147,13 +139,6 @@ private class RecoverForPythonFile( associateTypes(i, globalTypes) case _ => super.visitIdentifierAssignedToFieldLoad(i, fa) - override def getTypesFromCall(c: Call): Set[String] = c.name match - case ".listLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}list") - case ".tupleLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}tuple") - case ".dictLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}dict") - case ".setLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}set") - case _ => super.getTypesFromCall(c) - override def getFieldParents(fa: FieldAccess): Set[String] = if fa.method.name == "" then Set(fa.method.fullName) @@ -165,9 +150,6 @@ private class RecoverForPythonFile( else super.getFieldParents(fa) - private def isPyString(s: String): Boolean = - (s.startsWith("\"") || s.startsWith("'")) && (s.endsWith("\"") || s.endsWith("'")) - override def getLiteralType(l: Literal): Set[String] = (l.code match case code if code.toIntOption.isDefined => Some(s"${PythonAstVisitor.builtinPrefix}int") @@ -180,6 +162,9 @@ private class RecoverForPythonFile( case _ => None ).toSet + private def isPyString(s: String): Boolean = + (s.startsWith("\"") || s.startsWith("'")) && (s.endsWith("\"") || s.endsWith("'")) + override def createCallFromIdentifierTypeFullName( typeFullName: String, callName: String @@ -194,24 +179,8 @@ private class RecoverForPythonFile( Seq(t, callName).mkString(pathSep.toString) case _ => super.createCallFromIdentifierTypeFullName(typeFullName, callName) - override protected def postSetTypeInformation(): Unit = - cu.typeDecl - .map(t => - t -> t.inheritsFromTypeFullName.partition(itf => - symbolTable.contains(LocalVar(itf)) - ) - ) - .foreach { case (t, (identifierTypes, otherTypes)) => - val existingTypes = (identifierTypes ++ otherTypes).distinct - val resolvedTypes = identifierTypes.map(LocalVar.apply).flatMap(symbolTable.get) - if existingTypes != resolvedTypes && resolvedTypes.nonEmpty then - state.changesWereMade.compareAndExchange(false, true) - builder.setNodeProperty( - t, - PropertyNames.INHERITS_FROM_TYPE_FULL_NAME, - resolvedTypes - ) - } + override protected def isConstructor(name: String): Boolean = + name.nonEmpty && name.charAt(0).isUpper override def prepopulateSymbolTable(): Unit = cu.ast.isMethodRef.where( @@ -234,6 +203,25 @@ private class RecoverForPythonFile( super.prepopulateSymbolTable() end prepopulateSymbolTable + override protected def postSetTypeInformation(): Unit = + cu.typeDecl + .map(t => + t -> t.inheritsFromTypeFullName.partition(itf => + symbolTable.contains(LocalVar(itf)) + ) + ) + .foreach { case (t, (identifierTypes, otherTypes)) => + val existingTypes = (identifierTypes ++ otherTypes).distinct + val resolvedTypes = identifierTypes.map(LocalVar.apply).flatMap(symbolTable.get) + if existingTypes != resolvedTypes && resolvedTypes.nonEmpty then + state.changesWereMade.compareAndExchange(false, true) + builder.setNodeProperty( + t, + PropertyNames.INHERITS_FROM_TYPE_FULL_NAME, + resolvedTypes + ) + } + override protected def visitIdentifierAssignedToTypeRef( i: Identifier, t: TypeRef, @@ -244,4 +232,25 @@ private class RecoverForPythonFile( .map(td => symbolTable.append(CallAlias(i.name, rec), Set(td))) .headOption .getOrElse(super.visitIdentifierAssignedToTypeRef(i, t, rec)) + + override protected def getIndexAccessTypes(ia: Call): Set[String] = + ia.argument.argumentIndex(1).isCall.headOption match + case Some(c) => + getTypesFromCall(c).map(x => s"$x$pathSep${XTypeRecovery.DummyIndexAccess}") + case _ => super.getIndexAccessTypes(ia) + + override def getTypesFromCall(c: Call): Set[String] = c.name match + case ".listLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}list") + case ".tupleLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}tuple") + case ".dictLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}dict") + case ".setLiteral" => Set(s"${PythonAstVisitor.builtinPrefix}set") + case _ => super.getTypesFromCall(c) + + /** Replaces the `this` prefix with the Pythonic `self` prefix for instance methods of functions + * local to this compilation unit. + */ + private def fromNodeToLocalPythonKey(node: AstNode): Option[LocalKey] = + node match + case n: Method => Option(CallAlias(n.name, Option("self"))) + case _ => SBKey.fromNodeToLocalKey(node) end RecoverForPythonFile diff --git a/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/PySrc2CpgFixture.scala b/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/PySrc2CpgFixture.scala index 7d1d262c..4144d69c 100644 --- a/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/PySrc2CpgFixture.scala +++ b/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/PySrc2CpgFixture.scala @@ -1,6 +1,7 @@ package io.appthreat.pysrc2cpg import io.appthreat.dataflowengineoss.layers.dataflows.{OssDataFlow, OssDataFlowOptions} +import io.appthreat.dataflowengineoss.language.Path import io.appthreat.dataflowengineoss.queryengine.EngineContext import io.appthreat.dataflowengineoss.semanticsloader.FlowSemantic import io.appthreat.x2cpg.X2Cpg @@ -10,53 +11,56 @@ import io.shiftleft.codepropertygraph.Cpg import io.shiftleft.semanticcpg.language.{ICallResolver, NoResolve} import io.shiftleft.semanticcpg.layers.LayerCreatorContext -trait PythonFrontend extends LanguageFrontend { - override val fileSuffix: String = ".py" - - override def execute(sourceCodePath: java.io.File): Cpg = { - new Py2CpgOnFileSystem().createCpg(sourceCodePath.getAbsolutePath)(new Py2CpgOnFileSystemConfig()).get - } -} - -class PySrcTestCpg extends TestCpg with PythonFrontend { - private var _withOssDataflow = false - private var _extraFlows = List.empty[FlowSemantic] - - def withOssDataflow(value: Boolean = true): this.type = { - _withOssDataflow = value - this - } - - def withExtraFlows(value: List[FlowSemantic] = List.empty): this.type = { - _extraFlows = value - this - } - - override def applyPasses(): Unit = { - X2Cpg.applyDefaultOverlays(this) - new ImportsPass(this).createAndApply() - new ImportResolverPass(this).createAndApply() - new PythonInheritanceNamePass(this).createAndApply() - new DynamicTypeHintFullNamePass(this).createAndApply() - new PythonTypeRecoveryPass(this).createAndApply() - new PythonTypeHintCallLinker(this).createAndApply() - - // Some of passes above create new methods, so, we - // need to run the ASTLinkerPass one more time - new AstLinkerPass(this).createAndApply() - - if (_withOssDataflow) { - val context = new LayerCreatorContext(this) - val options = new OssDataFlowOptions(extraFlows = _extraFlows) - new OssDataFlow(options).run(context) - } - } -} - -class PySrc2CpgFixture(withOssDataflow: Boolean = false, extraFlows: List[FlowSemantic] = List.empty) - extends Code2CpgFixture(() => new PySrcTestCpg().withOssDataflow(withOssDataflow).withExtraFlows(extraFlows)) { - - implicit val resolver: ICallResolver = NoResolve - implicit val context: EngineContext = EngineContext() - -} +trait PythonFrontend extends LanguageFrontend: + override val fileSuffix: String = ".py" + + override def execute(sourceCodePath: java.io.File): Cpg = + new Py2CpgOnFileSystem().createCpg(sourceCodePath.getAbsolutePath)( + new Py2CpgOnFileSystemConfig() + ).get + +class PySrcTestCpg extends TestCpg with PythonFrontend: + private var _withOssDataflow = false + private var _extraFlows = List.empty[FlowSemantic] + + def withOssDataflow(value: Boolean = true): this.type = + _withOssDataflow = value + this + + def withExtraFlows(value: List[FlowSemantic] = List.empty): this.type = + _extraFlows = value + this + + override def applyPasses(): Unit = + X2Cpg.applyDefaultOverlays(this) + new ImportsPass(this).createAndApply() + new ImportResolverPass(this).createAndApply() + new PythonInheritanceNamePass(this).createAndApply() + new DynamicTypeHintFullNamePass(this).createAndApply() + new PythonTypeRecoveryPass(this).createAndApply() + new PythonTypeHintCallLinker(this).createAndApply() + + // Some of passes above create new methods, so, we + // need to run the ASTLinkerPass one more time + new AstLinkerPass(this).createAndApply() + + if _withOssDataflow then + val context = new LayerCreatorContext(this) + val options = new OssDataFlowOptions(extraFlows = _extraFlows) + new OssDataFlow(options).run(context) +end PySrcTestCpg + +class PySrc2CpgFixture( + withOssDataflow: Boolean = false, + extraFlows: List[FlowSemantic] = List.empty +) extends Code2CpgFixture(() => + new PySrcTestCpg().withOssDataflow(withOssDataflow).withExtraFlows(extraFlows) + ): + + implicit val resolver: ICallResolver = NoResolve + implicit val context: EngineContext = EngineContext() + + protected def flowToResultPairs(path: Path): List[(String, Integer)] = + path.resultPairs().collect { case (firstElement: String, secondElement: Option[Integer]) => + (firstElement, secondElement.getOrElse(-1)) + } diff --git a/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/dataflow/DataFlowTests.scala b/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/dataflow/DataFlowTests.scala index 4f2ab168..3ce42d16 100644 --- a/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/dataflow/DataFlowTests.scala +++ b/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/dataflow/DataFlowTests.scala @@ -1,122 +1,347 @@ package io.appthreat.pysrc2cpg.dataflow -import io.appthreat.pysrc2cpg.PySrc2CpgFixture import io.appthreat.dataflowengineoss.language.toExtendedCfgNode -import io.appthreat.dataflowengineoss.semanticsloader.FlowSemantic +import io.appthreat.dataflowengineoss.semanticsloader.{FlowMapping, FlowSemantic, PassThroughMapping} +import io.appthreat.pysrc2cpg.PySrc2CpgFixture import io.shiftleft.codepropertygraph.Cpg import io.shiftleft.codepropertygraph.generated.nodes.{Literal, Member, Method} -import io.shiftleft.semanticcpg.language._ +import io.shiftleft.semanticcpg.language.* import org.scalatest.Ignore import java.io.File -class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { +class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true): - "intra-procedural" in { - val cpg: Cpg = code(""" + "intra-procedural" in { + val cpg: Cpg = code(""" |a = 42 |c = foo(a, b) |print(c) |""".stripMargin) - val source = cpg.literal("42") - val sink = cpg.call.code("print.*").argument - sink.reachableByFlows(source).size shouldBe 1 - } + val source = cpg.literal("42") + val sink = cpg.call.code("print.*").argument + sink.reachableByFlows(source).size shouldBe 1 + } + + "intra-procedural 2" in { + val cpg = code( + """ + |x = foo(20) + |print(x) + |""".stripMargin + ) + + def source = cpg.literal("20") + def sink = cpg.call("print").argument + val flows = sink.reachableByFlows(source).l + flows.map(flowToResultPairs) shouldBe List(List(("foo(20)", 2), ("print(x)", 3)), List(("foo(20)", 2), ("print(x)", 3))) + } + + "flow from aliased literal to imported external method call return value" in { + val cpg = code( + """ + |from helpers import foo + |a = 20 + |print(foo(a)) + |""".stripMargin + ) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows.map(flowToResultPairs) shouldBe List(List(("a = 20", 3), ("foo(a)", 4))) + } + + "flow from literal directly used in imported external method call return value" in { + val cpg = code( + """ + |from helpers import foo + |print(foo(20)) + |""".stripMargin + ) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows.map(flowToResultPairs) shouldBe List(List(("foo(20)", 3))) + } + + "no flow from aliased literal to imported external method call return value given empty semantics" in { + val cpg = code( + """ + |from helpers import foo + |a = 20 + |print(foo(a)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("helpers.py:.foo", List()))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from aliased literal to imported external method call return value given receiver-only semantics" in { + val cpg = code( + """ + |from helpers import foo + |a = 20 + |print(foo(a)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("helpers.py:.foo", List(FlowMapping(0, 0))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from aliased literal to imported external method call return value given argument1-only semantics" in { + val cpg = code( + """ + |from helpers import foo + |a = 20 + |print(foo(a)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("helpers.py:.foo", List(FlowMapping(1, 1))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from literal to imported external method return value given empty semantics" in { + val cpg = code( + """ + |from helpers import foo + |print(foo(20)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("helpers.py:.foo", List()))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from literal to imported external method return value given receiver-only semantics" in { + val cpg = code( + """ + |from helpers import foo + |print(foo(20)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("helpers.py:.foo", List(FlowMapping(0, 0))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from literal to imported external method return value given argument1-only semantics" in { + val cpg = code( + """ + |from helpers import foo + |print(foo(20)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("helpers.py:.foo", List(FlowMapping(1, 1))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from aliased literal to method call return value given empty semantics" in { + val cpg = code( + """ + |def foo(x): + | return x + | + |a = 20 + |print(foo(a)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("Test0.py:.foo", List()))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from aliased literal to method call return value given receiver-only semantics" in { + val cpg = code( + """ + |def foo(x): + | return x + | + |a = 20 + |print(foo(a)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("Test0.py:.foo", List(FlowMapping(0, 0))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from aliased literal to method call return value given argument1-only semantics" ignore { + val cpg = code( + """ + |def foo(x): + | return x + | + |a = 20 + |print(foo(a)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("Test0.py:.foo", List(FlowMapping(1, 1))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } - "chained call" in { - val cpg: Cpg = code(""" + "no flow from literal to method call return value given empty semantics" ignore { + val cpg = code( + """ + |def foo(x): + | return x + | + |print(foo(20)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("Test0.py:.foo", List()))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from literal to method call return value given receiver-only semantics" ignore { + val cpg = code( + """ + |def foo(x): + | return x + | + |print(foo(20)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("Test0.py:.foo", List(FlowMapping(0, 0))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "no flow from literal to method call return value given argument1-only semantics" ignore { + val cpg = code( + """ + |def foo(x): + | return x + | + |print(foo(20)) + |""".stripMargin + ) + .withExtraFlows(List(FlowSemantic("Test0.py:.foo", List(FlowMapping(1, 1))))) + val source = cpg.literal("20").l + val sink = cpg.call("print").argument(1).l + val flows = sink.reachableByFlows(source).l + flows shouldBe empty + } + + "chained call" in { + val cpg: Cpg = code(""" |a = 42 |c = foo(a).bar() |sink(c) |""".stripMargin) - def source = cpg.literal("42") - def sink = cpg.call("sink").argument - sink.reachableByFlows(source).size shouldBe 1 - } + def source = cpg.literal("42") + def sink = cpg.call("sink").argument + sink.reachableByFlows(source).size shouldBe 1 + } - "inter procedural call 1" in { - val cpg: Cpg = code(""" + "inter procedural call 1" in { + val cpg: Cpg = code(""" |def foo(): | return 42 |bar = foo() |print(bar) |""".stripMargin) - def source = cpg.literal("42") - def sink = cpg.call("print") - sink.reachableByFlows(source).size shouldBe 1 - } + def source = cpg.literal("42") + def sink = cpg.call("print") + sink.reachableByFlows(source).size shouldBe 1 + } - "inter procedural call 2" in { - val cpg: Cpg = code(""" + "inter procedural call 2" in { + val cpg: Cpg = code(""" |def foo(input): | sink(input) |def main(): | source = 42 | foo(source) |""".stripMargin) - def source = cpg.literal("42") - def sink = cpg.call("sink") - sink.reachableByFlows(source).size shouldBe 1 - } + def source = cpg.literal("42") + def sink = cpg.call("sink") + sink.reachableByFlows(source).size shouldBe 1 + } - "flow from class variable to sink" in { - val cpg: Cpg = code(""" + "flow from class variable to sink" in { + val cpg: Cpg = code(""" |class Foo(): | x = 'sensitive' | def foo(self): | sink(self.x) |""".stripMargin) - val source = cpg.member(".*x.*").l - val sink = cpg.call(".*sink").argument(1).l - source.size shouldBe 1 - sink.size shouldBe 1 - sink.reachableByFlows(source).size shouldBe 1 - } - - "flow from class variable to sink in assignment" in { - val cpg: Cpg = code(""" + val source = cpg.member(".*x.*").l + val sink = cpg.call(".*sink").argument(1).l + source.size shouldBe 1 + sink.size shouldBe 1 + sink.reachableByFlows(source).size shouldBe 1 + } + + "flow from class variable to sink in assignment" in { + val cpg: Cpg = code(""" |class Foo(): | x = 'sensitive' | def foo(self): | a = sink(self.x) |""".stripMargin) - val source = cpg.member(".*x.*").l - val sink = cpg.call(".*sink").argument(1).l - source.size shouldBe 1 - sink.size shouldBe 1 - val flows = sink.reachableByFlows(source).l - flows.size shouldBe 1 - flows.head.elements.head match { - case member: Member => - member.name shouldBe "x" - case _ => fail() - } - } - - "flow from literal to class variable to sink in assignment" in { - val cpg: Cpg = code(""" + val source = cpg.member(".*x.*").l + val sink = cpg.call(".*sink").argument(1).l + source.size shouldBe 1 + sink.size shouldBe 1 + val flows = sink.reachableByFlows(source).l + flows.size shouldBe 1 + flows.head.elements.head match + case member: Member => + member.name shouldBe "x" + case _ => fail() + } + + "flow from literal to class variable to sink in assignment" in { + val cpg: Cpg = code(""" |class Foo(): | x = 'sensitive' | def foo(self): | a = sink(self.x) |""".stripMargin) - val source = cpg.literal.code(".*sensitive.*").l - val sink = cpg.call(".*sink").argument(1).l - source.size shouldBe 1 - sink.size shouldBe 1 - val flows = sink.reachableByFlows(source).l - flows.size shouldBe 1 - flows.head.elements.head match { - case literal: Literal => - literal.code shouldBe "'sensitive'" - case _ => fail() - } - } - - "flow from instance variable in constructor (MEMBER) to sink" in { - val cpg: Cpg = code(""" + val source = cpg.literal.code(".*sensitive.*").l + val sink = cpg.call(".*sink").argument(1).l + source.size shouldBe 1 + sink.size shouldBe 1 + val flows = sink.reachableByFlows(source).l + flows.size shouldBe 1 + flows.head.elements.head match + case literal: Literal => + literal.code shouldBe "'sensitive'" + case _ => fail() + } + + "flow from instance variable in constructor (MEMBER) to sink" in { + val cpg: Cpg = code(""" |class Foo: | def __init__(self): | self.x = 'sensitive' @@ -126,21 +351,20 @@ class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { | |""".stripMargin) - val source = cpg.member(".*x.*").l - val sink = cpg.call(".*sink").argument(1).l - source.size shouldBe 1 - sink.size shouldBe 1 - val flows = sink.reachableByFlows(source).l - flows.size shouldBe 1 - flows.head.elements.head match { - case member: Member => - member.name shouldBe "x" - case _ => fail() - } - } - - "flow from literal to instance variable in constructor (MEMBER) to sink" in { - val cpg: Cpg = code(""" + val source = cpg.member(".*x.*").l + val sink = cpg.call(".*sink").argument(1).l + source.size shouldBe 1 + sink.size shouldBe 1 + val flows = sink.reachableByFlows(source).l + flows.size shouldBe 1 + flows.head.elements.head match + case member: Member => + member.name shouldBe "x" + case _ => fail() + } + + "flow from literal to instance variable in constructor (MEMBER) to sink" in { + val cpg: Cpg = code(""" |class Foo: | def __init__(self): | self.x = 'sensitive' @@ -150,22 +374,21 @@ class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { | |""".stripMargin) - val source = cpg.literal.code(".*sensitive.*").l - val sink = cpg.call(".*sink").argument(1).l - source.size shouldBe 1 - sink.size shouldBe 1 - val flows = sink.reachableByFlows(source).l - flows.size shouldBe 1 - - flows.head.elements.head match { - case literal: Literal => - literal.code shouldBe "'sensitive'" - case _ => fail() + val source = cpg.literal.code(".*sensitive.*").l + val sink = cpg.call(".*sink").argument(1).l + source.size shouldBe 1 + sink.size shouldBe 1 + val flows = sink.reachableByFlows(source).l + flows.size shouldBe 1 + + flows.head.elements.head match + case literal: Literal => + literal.code shouldBe "'sensitive'" + case _ => fail() } - } - "flow from global variable to sink" in { - val cpg: Cpg = code(""" + "flow from global variable to sink" in { + val cpg: Cpg = code(""" |import requests |url = "https://app.commissionly.io/api/public/opportunity" | @@ -182,19 +405,19 @@ class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { |accountId="sometext" |response = client.post_data(data, accountId) |""".stripMargin) - val sourceUrlIdentifier = cpg.identifier(".*url.*").l - val sink = cpg.call("post").l - sourceUrlIdentifier.size shouldBe 2 - sink.size shouldBe 1 - sink.reachableByFlows(sourceUrlIdentifier).size shouldBe 2 - - val sourceUrlLiteral = cpg.literal(".*app.commissionly.io.*").l - sourceUrlLiteral.size shouldBe 1 - sink.reachableByFlows(sourceUrlLiteral).size shouldBe 1 - } - - "Flow correctly from parent scope to child function scope" in { - val cpg: Cpg = code(""" + val sourceUrlIdentifier = cpg.identifier(".*url.*").l + val sink = cpg.call("post").l + sourceUrlIdentifier.size shouldBe 2 + sink.size shouldBe 1 + sink.reachableByFlows(sourceUrlIdentifier).size shouldBe 2 + + val sourceUrlLiteral = cpg.literal(".*app.commissionly.io.*").l + sourceUrlLiteral.size shouldBe 1 + sink.reachableByFlows(sourceUrlLiteral).size shouldBe 1 + } + + "Flow correctly from parent scope to child function scope" in { + val cpg: Cpg = code(""" |def foo(u): | | x = 1 @@ -207,26 +430,26 @@ class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { | |""".stripMargin) - val sink1 = cpg.call("print").l - val sink2 = cpg.call("debug").l - sink1.size shouldBe 1 - sink2.size shouldBe 1 + val sink1 = cpg.call("print").l + val sink2 = cpg.call("debug").l + sink1.size shouldBe 1 + sink2.size shouldBe 1 - val iSrc = cpg.method("foo").ast.isIdentifier.name("x").lineNumber(4).l - iSrc.size shouldBe 1 - sink1.reachableBy(iSrc).dedup.size shouldBe 1 + val iSrc = cpg.method("foo").ast.isIdentifier.name("x").lineNumber(4).l + iSrc.size shouldBe 1 + sink1.reachableBy(iSrc).dedup.size shouldBe 1 - val lSrc = cpg.method("foo").ast.isLiteral.code("1").lineNumber(4).l - lSrc.size shouldBe 1 - sink1.reachableBy(lSrc).size shouldBe 1 + val lSrc = cpg.method("foo").ast.isLiteral.code("1").lineNumber(4).l + lSrc.size shouldBe 1 + sink1.reachableBy(lSrc).size shouldBe 1 - val pSrc = cpg.method("foo").parameter.nameExact("u").l - pSrc.size shouldBe 1 - sink2.reachableBy(pSrc).size shouldBe 1 - } + val pSrc = cpg.method("foo").parameter.nameExact("u").l + pSrc.size shouldBe 1 + sink2.reachableBy(pSrc).size shouldBe 1 + } - "flow from function param to sink" in { - val cpg: Cpg = code(""" + "flow from function param to sink" in { + val cpg: Cpg = code(""" |import requests | |class TestClient: @@ -242,20 +465,20 @@ class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { | auth=(self.user, self.password) | ) |""".stripMargin) - val sourceMember = cpg.member(".*password.*").l - val sinkPost = cpg.call.methodFullName(".*requests.*post.*").l - val flowsPost = sinkPost.reachableByFlows(sourceMember).l - flowsPost.size shouldBe 1 + val sourceMember = cpg.member(".*password.*").l + val sinkPost = cpg.call.methodFullName(".*requests.*post.*").l + val flowsPost = sinkPost.reachableByFlows(sourceMember).l + flowsPost.size shouldBe 1 - val sourceParam = cpg.identifier("accountId").l - val sinkGet = cpg.call.methodFullName(".*requests.*get.*").l + val sourceParam = cpg.identifier("accountId").l + val sinkGet = cpg.call.methodFullName(".*requests.*get.*").l - val flowsGet = sinkGet.reachableByFlows(sourceParam).l - flowsGet.size shouldBe 2 - } + val flowsGet = sinkGet.reachableByFlows(sourceParam).l + flowsGet.size shouldBe 2 + } - "flow from index access to index access" in { - val cpg: Cpg = code(""" + "flow from index access to index access" in { + val cpg: Cpg = code(""" | |def foo(): | y = dict() @@ -263,14 +486,14 @@ class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { | sink(y) |""".stripMargin) - val sources = cpg.identifier("x").l - val sinks = cpg.call("sink").argument.l - val flows = sinks.reachableByFlows(sources) - flows.size shouldBe 1 - } + val sources = cpg.identifier("x").l + val sinks = cpg.call("sink").argument.l + val flows = sinks.reachableByFlows(sources) + flows.size shouldBe 1 + } - "flow from expression that taints global variable to sink" in { - val cpg: Cpg = code(""" + "flow from expression that taints global variable to sink" in { + val cpg: Cpg = code(""" |d = { | 'x': F.sum('x'), | 'y': F.sum('y'), @@ -282,52 +505,51 @@ class DataFlowTests extends PySrc2CpgFixture(withOssDataflow = true) { | return sink(d) |""".stripMargin) - val sources = cpg.call(".indexAccess").argument.isIdentifier.l - val sinks = cpg.call("sink").l - sinks.reachableByFlows(sources).size should not be 0 - } + val sources = cpg.call(".indexAccess").argument.isIdentifier.l + val sinks = cpg.call("sink").l + sinks.reachableByFlows(sources).size should not be 0 + } - "lookup of __init__ call" in { - val cpg = code(""" + "lookup of __init__ call" in { + val cpg = code(""" |from models import Foo |foo = Foo(x,y,z) |""".stripMargin) - .moreCode( - """ + .moreCode( + """ |class Foo: | def __init__(self, a, b, c): | println("foo") | pass |""".stripMargin, - "models.py" - ) + "models.py" + ) - val List(method: Method) = cpg.identifier.name("foo").inAssignment.source.isCall.callee.l - method.fullName shouldBe "models.py:.Foo.__init__" - val List(typeDeclFullName) = method.typeDecl.fullName.l - typeDeclFullName shouldBe "models.py:.Foo" - } + val List(method: Method) = cpg.identifier.name("foo").inAssignment.source.isCall.callee.l + method.fullName shouldBe "models.py:.Foo.__init__" + val List(typeDeclFullName) = method.typeDecl.fullName.l + typeDeclFullName shouldBe "models.py:.Foo" + } - "lookup of __init__ call even when hidden in base class" in { - val cpg = code(""" + "lookup of __init__ call even when hidden in base class" in { + val cpg = code(""" |from models import Foo |foo = Foo(x,y,z) |""".stripMargin) - .moreCode( - """ + .moreCode( + """ |class Foo(SomeType): | pass |""".stripMargin, - "models.py" - ) + "models.py" + ) - val List(method: Method) = cpg.identifier.name("foo").inAssignment.source.isCall.callee.l - method.fullName shouldBe "models.py:.Foo.__init__" - val List(typeDeclFullName) = method.typeDecl.fullName.l - typeDeclFullName shouldBe "models.py:.Foo" - } - -} + val List(method: Method) = cpg.identifier.name("foo").inAssignment.source.isCall.callee.l + method.fullName shouldBe "models.py:.Foo.__init__" + val List(typeDeclFullName) = method.typeDecl.fullName.l + typeDeclFullName shouldBe "models.py:.Foo" + } +end DataFlowTests class RegexDefinedFlowsDataFlowTests extends PySrc2CpgFixture( @@ -335,40 +557,50 @@ class RegexDefinedFlowsDataFlowTests extraFlows = List( FlowSemantic.from("^path.*\\.sanitizer$", List((0, 0), (1, 1)), regex = true), FlowSemantic.from("^foo.*\\.sanitizer.*", List((0, 0), (1, 1)), regex = true), - FlowSemantic.from("^foo.*\\.create_sanitizer\\.\\.sanitize", List((0, 0), (1, 1)), regex = true), + FlowSemantic.from( + "^foo.*\\.create_sanitizer\\.\\.sanitize", + List((0, 0), (1, 1)), + regex = true + ), FlowSemantic - .from( - "requests.py:.post", - List((0, 0), (1, "url", -1), (2, "body", -1), (1, "url", 1, "url"), (2, "body", 2, "body")) - ), + .from( + "requests.py:.post", + List( + (0, 0), + (1, "url", -1), + (2, "body", -1), + (1, "url", 1, "url"), + (2, "body", 2, "body") + ) + ), FlowSemantic.from("cross_taint.py:.go", List((0, 0), (1, 1), (1, "a", 2, "b"))) ) - ) { + ): - "regex matched semantic for imported method" should { - lazy val cpg = code( - """ + "regex matched semantic for imported method" should { + lazy val cpg = code( + """ |from path import sanitizer | |source = 1 |x = sanitizer(source) |sink(x) |""".stripMargin, - Seq("foo.py").mkString(java.io.File.separator) - ) + Seq("foo.py").mkString(java.io.File.separator) + ) - "register that sanitizer kills the flow on the parameter" in { - def source = cpg.literal("1") - def sink = cpg.call("sink") + "register that sanitizer kills the flow on the parameter" in { + def source = cpg.literal("1") + def sink = cpg.call("sink") - sink.reachableBy(source).size shouldBe 0 - } + sink.reachableBy(source).size shouldBe 0 + } - } + } - "regex matched semantic for more than one imported method" should { - lazy val cpg = code( - """ + "regex matched semantic for more than one imported method" should { + lazy val cpg = code( + """ |from foo import sanitizerFoo, sanitizerBar | |source = 1 @@ -376,21 +608,21 @@ class RegexDefinedFlowsDataFlowTests |y = sanitizerBar(source) |sink(x, y) |""".stripMargin, - Seq("foo.py").mkString(java.io.File.separator) - ) + Seq("foo.py").mkString(java.io.File.separator) + ) - "register that all sanitizers kill the flow on the parameter" in { - def source = cpg.literal("1") - def sink = cpg.call("sink") + "register that all sanitizers kill the flow on the parameter" in { + def source = cpg.literal("1") + def sink = cpg.call("sink") - sink.reachableBy(source).size shouldBe 0 - } + sink.reachableBy(source).size shouldBe 0 + } - } + } - "regex matched semantic for a dummy type resulting from type recovery" should { - val cpg = code( - """ + "regex matched semantic for a dummy type resulting from type recovery" should { + val cpg = code( + """ |from foo import create_sanitizer | |source = 1 @@ -398,20 +630,20 @@ class RegexDefinedFlowsDataFlowTests |y = x.sanitize(source) |sink(y) |""".stripMargin, - Seq("foo.py").mkString(java.io.File.separator) - ) + Seq("foo.py").mkString(java.io.File.separator) + ) - "register that the call off of a return value has no flow" in { - def source = cpg.literal("1") - def sink = cpg.call("sink") + "register that the call off of a return value has no flow" in { + def source = cpg.literal("1") + def sink = cpg.call("sink") - sink.reachableBy(source).size shouldBe 0 - } + sink.reachableBy(source).size shouldBe 0 + } - } + } - "flows to parameterized arguments" should { - val cpg = code(""" + "flows to parameterized arguments" should { + val cpg = code(""" |import requests |def foo(): | orderId = "Mysource" @@ -422,16 +654,16 @@ class RegexDefinedFlowsDataFlowTests | ) |""".stripMargin) - "have summarized flows accurately pass parameterized argument behaviour" in { - val source = cpg.identifier("orderId") - val sink = cpg.call("post") + "have summarized flows accurately pass parameterized argument behaviour" in { + val source = cpg.identifier("orderId") + val sink = cpg.call("post") - sink.reachableBy(source).size shouldBe 2 + sink.reachableBy(source).size shouldBe 2 + } } - } - "flows across named parameterized arguments" should { - val cpg = code(""" + "flows across named parameterized arguments" should { + val cpg = code(""" |import cross_taint | |def foo(): @@ -441,44 +673,44 @@ class RegexDefinedFlowsDataFlowTests | sink(transport) |""".stripMargin) - "have passed taint from one parameter to the next" in { - val source = cpg.literal("\"Mysource\"") - val sink = cpg.call("sink") + "have passed taint from one parameter to the next" in { + val source = cpg.literal("\"Mysource\"") + val sink = cpg.call("sink") - sink.reachableBy(source).size shouldBe 1 + sink.reachableBy(source).size shouldBe 1 + } } - } - "flows from receivers" should { - val cpg = code(""" + "flows from receivers" should { + val cpg = code(""" |class Foo: | def func(): | return "x" |print(Foo.func()) |""".stripMargin) - "be found" in { - val src = cpg.call.code("Foo.func").l - val snk = cpg.call("print").l - snk.argument.reachableByFlows(src).size shouldBe 1 + "be found" in { + val src = cpg.call.code("Foo.func").l + val snk = cpg.call("print").l + snk.argument.reachableByFlows(src).size shouldBe 1 + } } - } - "flows from receivers directly" should { - val cpg = code(""" + "flows from receivers directly" should { + val cpg = code(""" |class Foo: | def func(): | return "x" |print(Foo.func()) |""".stripMargin) - "be found" in { - val src = cpg.identifier("Foo").l - val snk = cpg.call("print").l - snk.reachableByFlows(src).size shouldBe 2 - } - } - "Import statement with method ref sample four" in { - val controller = - """ + "be found" in { + val src = cpg.identifier("Foo").l + val snk = cpg.call("print").l + snk.reachableByFlows(src).size shouldBe 2 + } + } + "Import statement with method ref sample four" in { + val controller = + """ |from django.contrib import admin |from django.urls import path |from django.conf.urls import url @@ -488,21 +720,21 @@ class RegexDefinedFlowsDataFlowTests | url(r'allPage', all_page) |] |""".stripMargin - val views = - """ + val views = + """ |def all_page(request): | print("All pages") |""".stripMargin - val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) - .moreCode(views, Seq("controller", "views.py").mkString(File.separator)) + val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) + .moreCode(views, Seq("controller", "views.py").mkString(File.separator)) - val args = cpg.call.methodFullName("django.*[.](path|url)").l.head.argument.l - args.size shouldBe 3 - } + val args = cpg.call.methodFullName("django.*[.](path|url)").l.head.argument.l + args.size shouldBe 3 + } - "Import statement with method ref sample five" in { - val controller = - """ + "Import statement with method ref sample five" in { + val controller = + """ |from django.contrib import admin |from django.urls import path |from django.conf.urls import url @@ -512,20 +744,20 @@ class RegexDefinedFlowsDataFlowTests | url(r'allPage', all_page) |] |""".stripMargin - val views = - """ + val views = + """ |def all_page(request): | print("All pages") |""".stripMargin - val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) - .moreCode(views, Seq("student", "views.py").mkString(File.separator)) + val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) + .moreCode(views, Seq("student", "views.py").mkString(File.separator)) - val args = cpg.call.methodFullName("django.*[.](path|url)").l.head.argument.l - args.size shouldBe 3 - } + val args = cpg.call.methodFullName("django.*[.](path|url)").l.head.argument.l + args.size shouldBe 3 + } - "flows via tuple literal" should { - val cpg = code(""" + "flows via tuple literal" should { + val cpg = code(""" |a = 1 |b = 2 |c = 3 @@ -535,21 +767,21 @@ class RegexDefinedFlowsDataFlowTests |sink1(b) |sink2(x) |""".stripMargin) - "not cross-taint due to 'pass through' semantics" in { - val src = cpg.literal("1").l - val snk = cpg.call("sink1").l - snk.reachableByFlows(src).size shouldBe 0 - } - - "taint the return value due to 'pass through' semantics" in { - val src = cpg.call.nameExact(".tupleLiteral").l - val snk = cpg.call("sink2").l - snk.reachableByFlows(src).size shouldBe 1 + "not cross-taint due to 'pass through' semantics" in { + val src = cpg.literal("1").l + val snk = cpg.call("sink1").l + snk.reachableByFlows(src).size shouldBe 0 + } + + "taint the return value due to 'pass through' semantics" in { + val src = cpg.call.nameExact(".tupleLiteral").l + val snk = cpg.call("sink2").l + snk.reachableByFlows(src).size shouldBe 1 + } } - } - "Exception block flow sample one" in { - val cpg: Cpg = code(""" + "Exception block flow sample one" in { + val cpg: Cpg = code(""" |import logging |tmp = logging.getLogger(__name__) | @@ -562,15 +794,15 @@ class RegexDefinedFlowsDataFlowTests | tmp.error(f"Failure: {accountId}") | return None |""".stripMargin) - val sources = cpg.identifier(".*account.*").lineNumber(6).l - val sinks = cpg.call.methodFullName(".*log.*(debug|info|error).*").l - val flows = sinks.reachableByFlows(sources).l - flows.size shouldBe 2 - } - - // TODO: Need to fix this scenario. This use case is not working across the frontend. Had tested it for Java as well. - "Exception block flow sample two" ignore { - val cpg: Cpg = code(""" + val sources = cpg.identifier(".*account.*").lineNumber(6).l + val sinks = cpg.call.methodFullName(".*log.*(debug|info|error).*").l + val flows = sinks.reachableByFlows(sources).l + flows.size shouldBe 2 + } + + // TODO: Need to fix this scenario. This use case is not working across the frontend. Had tested it for Java as well. + "Exception block flow sample two" ignore { + val cpg: Cpg = code(""" |import logging |tmp = logging.getLogger(__name__) | @@ -583,10 +815,9 @@ class RegexDefinedFlowsDataFlowTests | tmp.error(f"Failure: {accountId}") | return None |""".stripMargin) - val sources = cpg.identifier(".*account.*").lineNumber(6).l - val sinks = cpg.call.methodFullName(".*log.*(debug|info|error).*").l - val flows = sinks.reachableByFlows(sources).l - flows.size shouldBe 2 - } - -} + val sources = cpg.identifier(".*account.*").lineNumber(6).l + val sinks = cpg.call.methodFullName(".*log.*(debug|info|error).*").l + val flows = sinks.reachableByFlows(sources).l + flows.size shouldBe 2 + } +end RegexDefinedFlowsDataFlowTests diff --git a/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/passes/TypeRecoveryPassTests.scala b/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/passes/TypeRecoveryPassTests.scala index 896697fd..152e9d78 100644 --- a/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/passes/TypeRecoveryPassTests.scala +++ b/platform/frontends/pysrc2cpg/src/test/scala/io/appthreat/pysrc2cpg/passes/TypeRecoveryPassTests.scala @@ -9,10 +9,10 @@ import io.appthreat.x2cpg.passes.frontend.ImportsPass.* import io.shiftleft.semanticcpg.language.NoResolve import scala.collection.immutable.Seq -class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { +class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false): - "literals declared from built-in types" should { - lazy val cpg = code(""" + "literals declared from built-in types" should { + lazy val cpg = code(""" |x = 123 | |def foo_shadowing(): @@ -25,35 +25,47 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { |z.append(4) |""".stripMargin).cpg - "resolve 'x' identifier types despite shadowing" in { - val List(xOuterScope, xInnerScope) = cpg.identifier("x").take(2).l - xOuterScope.dynamicTypeHintFullName shouldBe Seq("__builtin.int") - xInnerScope.dynamicTypeHintFullName shouldBe Seq("__builtin.int") + "resolve 'x' identifier types despite shadowing" in { + val List(xOuterScope, xInnerScope) = cpg.identifier("x").take(2).l + xOuterScope.dynamicTypeHintFullName shouldBe Seq("__builtin.int") + xInnerScope.dynamicTypeHintFullName shouldBe Seq("__builtin.int") + } + + "resolve 'y' and 'z' identifier collection types" in { + val List(zDict, zList, zTuple) = cpg.identifier("z").take(3).l + zDict.dynamicTypeHintFullName shouldBe Seq( + "__builtin.dict", + "__builtin.list", + "__builtin.tuple" + ) + zList.dynamicTypeHintFullName shouldBe Seq( + "__builtin.dict", + "__builtin.list", + "__builtin.tuple" + ) + zTuple.dynamicTypeHintFullName shouldBe Seq( + "__builtin.dict", + "__builtin.list", + "__builtin.tuple" + ) + } + + "resolve 'z' identifier calls conservatively" in { + val List(zAppend) = cpg.call("append").l + zAppend.methodFullName shouldBe "" + // Since we don't have method nodes with this full name, this should belong to the call linker namespace + zAppend.callee.astParentFullName.headOption shouldBe Some(XTypeHintCallLinker.namespace) + zAppend.dynamicTypeHintFullName shouldBe Seq( + "__builtin.dict.append", + "__builtin.list.append", + "__builtin.tuple.append" + ) + } } - "resolve 'y' and 'z' identifier collection types" in { - val List(zDict, zList, zTuple) = cpg.identifier("z").take(3).l - zDict.dynamicTypeHintFullName shouldBe Seq("__builtin.dict", "__builtin.list", "__builtin.tuple") - zList.dynamicTypeHintFullName shouldBe Seq("__builtin.dict", "__builtin.list", "__builtin.tuple") - zTuple.dynamicTypeHintFullName shouldBe Seq("__builtin.dict", "__builtin.list", "__builtin.tuple") - } - - "resolve 'z' identifier calls conservatively" in { - val List(zAppend) = cpg.call("append").l - zAppend.methodFullName shouldBe "" - // Since we don't have method nodes with this full name, this should belong to the call linker namespace - zAppend.callee.astParentFullName.headOption shouldBe Some(XTypeHintCallLinker.namespace) - zAppend.dynamicTypeHintFullName shouldBe Seq( - "__builtin.dict.append", - "__builtin.list.append", - "__builtin.tuple.append" - ) - } - } + "call from a function from an external type" should { - "call from a function from an external type" should { - - lazy val cpg = code(""" + lazy val cpg = code(""" |from slack_sdk import WebClient |from sendgrid import SendGridAPIClient | @@ -66,54 +78,54 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { |response = sg.send(message) |""".stripMargin).cpg - "resolve correct imports via tag nodes" in { - val List( - webClientM: UnknownMethod, - webClientT: UnknownTypeDecl, - sendGridM: UnknownMethod, - sendGridT: UnknownTypeDecl - ) = cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - webClientM.fullName shouldBe "slack_sdk.py:.WebClient.__init__" - webClientT.fullName shouldBe "slack_sdk.py:.WebClient" - sendGridM.fullName shouldBe "sendgrid.py:.SendGridAPIClient.__init__" - sendGridT.fullName shouldBe "sendgrid.py:.SendGridAPIClient" - } - - "resolve 'sg' identifier types from import information" in { - val List(sgAssignment, sgElseWhere) = cpg.identifier("sg").take(2).l - sgAssignment.typeFullName shouldBe "sendgrid.py:.SendGridAPIClient" - sgElseWhere.typeFullName shouldBe "sendgrid.py:.SendGridAPIClient" - } - - "resolve 'sg' call path from import information" in { - val List(apiClient) = cpg.call("SendGridAPIClient").l - apiClient.methodFullName shouldBe "sendgrid.py:.SendGridAPIClient.__init__" - val List(sendCall) = cpg.call("send").l - sendCall.methodFullName shouldBe "sendgrid.py:.SendGridAPIClient.send" - } - - "resolve 'client' identifier types from import information" in { - val List(clientAssignment, clientElseWhere) = cpg.identifier("client").take(2).l - clientAssignment.typeFullName shouldBe "slack_sdk.py:.WebClient" - clientElseWhere.typeFullName shouldBe "slack_sdk.py:.WebClient" - } - - "resolve 'client' call path from identifier in child scope" in { - val List(client) = cpg.call("WebClient").l - client.methodFullName shouldBe "slack_sdk.py:.WebClient.__init__" - val List(postMessage) = cpg.call("chat_postMessage").l - postMessage.methodFullName shouldBe "slack_sdk.py:.WebClient.chat_postMessage" - } + "resolve correct imports via tag nodes" in { + val List( + webClientM: UnknownMethod, + webClientT: UnknownTypeDecl, + sendGridM: UnknownMethod, + sendGridT: UnknownTypeDecl + ) = cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + webClientM.fullName shouldBe "slack_sdk.py:.WebClient.__init__" + webClientT.fullName shouldBe "slack_sdk.py:.WebClient" + sendGridM.fullName shouldBe "sendgrid.py:.SendGridAPIClient.__init__" + sendGridT.fullName shouldBe "sendgrid.py:.SendGridAPIClient" + } + + "resolve 'sg' identifier types from import information" in { + val List(sgAssignment, sgElseWhere) = cpg.identifier("sg").take(2).l + sgAssignment.typeFullName shouldBe "sendgrid.py:.SendGridAPIClient" + sgElseWhere.typeFullName shouldBe "sendgrid.py:.SendGridAPIClient" + } + + "resolve 'sg' call path from import information" in { + val List(apiClient) = cpg.call("SendGridAPIClient").l + apiClient.methodFullName shouldBe "sendgrid.py:.SendGridAPIClient.__init__" + val List(sendCall) = cpg.call("send").l + sendCall.methodFullName shouldBe "sendgrid.py:.SendGridAPIClient.send" + } + + "resolve 'client' identifier types from import information" in { + val List(clientAssignment, clientElseWhere) = cpg.identifier("client").take(2).l + clientAssignment.typeFullName shouldBe "slack_sdk.py:.WebClient" + clientElseWhere.typeFullName shouldBe "slack_sdk.py:.WebClient" + } + + "resolve 'client' call path from identifier in child scope" in { + val List(client) = cpg.call("WebClient").l + client.methodFullName shouldBe "slack_sdk.py:.WebClient.__init__" + val List(postMessage) = cpg.call("chat_postMessage").l + postMessage.methodFullName shouldBe "slack_sdk.py:.WebClient.chat_postMessage" + } + + "resolve a dummy 'send' return value from sg.send" in { + val List(postMessage) = cpg.identifier("response").l + postMessage.typeFullName shouldBe "sendgrid.py:.SendGridAPIClient.send." + } - "resolve a dummy 'send' return value from sg.send" in { - val List(postMessage) = cpg.identifier("response").l - postMessage.typeFullName shouldBe "sendgrid.py:.SendGridAPIClient.send." } - } - - "type recovery on class members" should { - lazy val cpg = code(""" + "type recovery on class members" should { + lazy val cpg = code(""" |from flask_sqlalchemy import SQLAlchemy | |db = SQLAlchemy() @@ -139,35 +151,35 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | } |""".stripMargin).cpg - "resolve 'db' identifier types from import information" in { - val List(clientAssignment, clientElseWhere) = cpg.identifier("db").take(2).l - clientAssignment.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" - clientElseWhere.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" - } - - "resolve the 'SQLAlchemy' constructor in the module" in { - val Some(client) = cpg.call("SQLAlchemy").headOption: @unchecked - client.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.__init__" - } - - "resolve 'User' field types" in { - val List(id, firstname, age, address) = - cpg.identifier.nameExact("id", "firstname", "age", "address").l.takeRight(4) - id.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" - firstname.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" - age.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" - address.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" - } + "resolve 'db' identifier types from import information" in { + val List(clientAssignment, clientElseWhere) = cpg.identifier("db").take(2).l + clientAssignment.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" + clientElseWhere.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" + } + + "resolve the 'SQLAlchemy' constructor in the module" in { + val Some(client) = cpg.call("SQLAlchemy").headOption: @unchecked + client.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.__init__" + } + + "resolve 'User' field types" in { + val List(id, firstname, age, address) = + cpg.identifier.nameExact("id", "firstname", "age", "address").l.takeRight(4) + id.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" + firstname.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" + age.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" + address.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column" + } + + "resolve the 'Column' constructor for a class member" in { + val Some(columnConstructor) = cpg.call("Column").headOption: @unchecked + columnConstructor.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column.__init__" + } - "resolve the 'Column' constructor for a class member" in { - val Some(columnConstructor) = cpg.call("Column").headOption: @unchecked - columnConstructor.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.Column.__init__" } - } - - "recovering paths for built-in calls" should { - lazy val cpg = code(""" + "recovering paths for built-in calls" should { + lazy val cpg = code(""" |print("Hello world") |max(1, 2) | @@ -176,32 +188,32 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { |x = abs(-1) |""".stripMargin).cpg - "resolve 'print' and 'max' calls" in { - val Some(printCall) = cpg.call("print").headOption: @unchecked - printCall.methodFullName shouldBe "__builtin.print" - val Some(maxCall) = cpg.call("max").headOption: @unchecked - maxCall.methodFullName shouldBe "__builtin.max" - } + "resolve 'print' and 'max' calls" in { + val Some(printCall) = cpg.call("print").headOption: @unchecked + printCall.methodFullName shouldBe "__builtin.print" + val Some(maxCall) = cpg.call("max").headOption: @unchecked + maxCall.methodFullName shouldBe "__builtin.max" + } - "conservatively present either option when an imported function uses the same name as a builtin" in { - val Some(absCall) = cpg.call("abs").headOption: @unchecked - absCall.dynamicTypeHintFullName shouldBe Seq("foo.py:.abs", "__builtin.abs") - } + "conservatively present either option when an imported function uses the same name as a builtin" in { + val Some(absCall) = cpg.call("abs").headOption: @unchecked + absCall.dynamicTypeHintFullName shouldBe Seq("foo.py:.abs", "__builtin.abs") + } - } + } - "recovering module members across modules" should { - lazy val cpg = code( - """ + "recovering module members across modules" should { + lazy val cpg = code( + """ |from flask_sqlalchemy import SQLAlchemy | |x = 1 |y = "test" |db = SQLAlchemy() |""".stripMargin, - "foo.py" - ).moreCode( - """ + "foo.py" + ).moreCode( + """ |import foo | |z = foo.x @@ -213,81 +225,85 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | |foo.db.deleteTable() |""".stripMargin, - "bar.py" - ).cpg - - "resolve correct imports via tag nodes" in { - val List(foo1: UnknownMethod, foo2: UnknownTypeDecl) = - cpg.file(".*foo.py").ast.isCall.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - foo1.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.__init__" - foo2.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" - val List(bar1: ResolvedTypeDecl, bar2: ResolvedMethod) = - cpg.file(".*bar.py").ast.isCall.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - bar1.fullName shouldBe "foo.py:" - bar2.fullName shouldBe "foo.py:" - } + "bar.py" + ).cpg + + "resolve correct imports via tag nodes" in { + val List(foo1: UnknownMethod, foo2: UnknownTypeDecl) = + cpg.file(".*foo.py").ast.isCall.where( + _.referencedImports + ).tag.toResolvedImport.toList: @unchecked + foo1.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.__init__" + foo2.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" + val List(bar1: ResolvedTypeDecl, bar2: ResolvedMethod) = + cpg.file(".*bar.py").ast.isCall.where( + _.referencedImports + ).tag.toResolvedImport.toList: @unchecked + bar1.fullName shouldBe "foo.py:" + bar2.fullName shouldBe "foo.py:" + } + + "resolve 'x' and 'y' locally under foo.py" in { + val Some(x) = cpg.file.name(".*foo.*").ast.isIdentifier.name("x").headOption: @unchecked + x.typeFullName shouldBe "__builtin.int" + val Some(y) = cpg.file.name(".*foo.*").ast.isIdentifier.name("y").headOption: @unchecked + y.typeFullName shouldBe "__builtin.str" + } + + "resolve 'foo.x' and 'foo.y' field access primitive types correctly" in { + val List(z1, z2) = cpg.file + .name(".*bar.*") + .ast + .isIdentifier + .name("z") + .l + z1.typeFullName shouldBe "__builtin.str" + z1.dynamicTypeHintFullName shouldBe Seq("__builtin.int") + z2.typeFullName shouldBe "__builtin.str" + z2.dynamicTypeHintFullName shouldBe Seq("__builtin.int") + } + + "resolve 'foo.d' field access object types correctly" in { + val Some(d) = cpg.file + .name(".*bar.*") + .ast + .isIdentifier + .name("d") + .headOption: @unchecked + d.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" + d.dynamicTypeHintFullName shouldBe Seq() + } + + "resolve a 'createTable' call indirectly from 'foo.d' field access correctly" in { + val List(d) = cpg.file + .name(".*bar.*") + .ast + .isCall + .name("createTable") + .l + d.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.createTable" + d.dynamicTypeHintFullName shouldBe Seq() + d.callee(NoResolve).isExternal.headOption shouldBe Some(true) + } + + "resolve a 'deleteTable' call directly from 'foo.db' field access correctly" in { + val List(d) = cpg.file + .name(".*bar.*") + .ast + .isCall + .name("deleteTable") + .l + + d.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.deleteTable" + d.dynamicTypeHintFullName shouldBe Seq() + d.callee(NoResolve).isExternal.headOption shouldBe Some(true) + } - "resolve 'x' and 'y' locally under foo.py" in { - val Some(x) = cpg.file.name(".*foo.*").ast.isIdentifier.name("x").headOption: @unchecked - x.typeFullName shouldBe "__builtin.int" - val Some(y) = cpg.file.name(".*foo.*").ast.isIdentifier.name("y").headOption: @unchecked - y.typeFullName shouldBe "__builtin.str" } - "resolve 'foo.x' and 'foo.y' field access primitive types correctly" in { - val List(z1, z2) = cpg.file - .name(".*bar.*") - .ast - .isIdentifier - .name("z") - .l - z1.typeFullName shouldBe "__builtin.str" - z1.dynamicTypeHintFullName shouldBe Seq("__builtin.int") - z2.typeFullName shouldBe "__builtin.str" - z2.dynamicTypeHintFullName shouldBe Seq("__builtin.int") - } - - "resolve 'foo.d' field access object types correctly" in { - val Some(d) = cpg.file - .name(".*bar.*") - .ast - .isIdentifier - .name("d") - .headOption: @unchecked - d.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" - d.dynamicTypeHintFullName shouldBe Seq() - } - - "resolve a 'createTable' call indirectly from 'foo.d' field access correctly" in { - val List(d) = cpg.file - .name(".*bar.*") - .ast - .isCall - .name("createTable") - .l - d.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.createTable" - d.dynamicTypeHintFullName shouldBe Seq() - d.callee(NoResolve).isExternal.headOption shouldBe Some(true) - } - - "resolve a 'deleteTable' call directly from 'foo.db' field access correctly" in { - val List(d) = cpg.file - .name(".*bar.*") - .ast - .isCall - .name("deleteTable") - .l - - d.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.deleteTable" - d.dynamicTypeHintFullName shouldBe Seq() - d.callee(NoResolve).isExternal.headOption shouldBe Some(true) - } - - } - - "type recovery for a field imported as an individual component" should { - lazy val cpg = code( - """import app + "type recovery for a field imported as an individual component" should { + lazy val cpg = code( + """import app |from flask import jsonify | |def store(): @@ -299,104 +315,113 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | except Exception as e: | return 'There was an issue adding your task' |""".stripMargin, - "UserController.py" - ).moreCode( - """ + "UserController.py" + ).moreCode( + """ |from flask_sqlalchemy import SQLAlchemy | |db = SQLAlchemy() |""".stripMargin, - "app.py" - ).cpg - - "resolve correct imports via tag nodes" in { - val List(a: ResolvedTypeDecl, b: ResolvedMethod, c: UnknownImport, d: ResolvedMember) = - cpg.file(".*UserController.py").ast.isCall.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - a.fullName shouldBe "app.py:" - b.fullName shouldBe "app.py:" - c.path shouldBe "flask.py:.jsonify" - d.basePath shouldBe "app.py:" - d.memberName shouldBe "db" - - val List(sqlAlchemyM: UnknownMethod, sqlAlchemyT: UnknownTypeDecl) = - cpg.file(".*app.py").ast.isCall.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - sqlAlchemyM.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.__init__" - sqlAlchemyT.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" - } - - "be determined as a variable reference and have its type recovered correctly" in { - cpg.identifier("db").map(_.typeFullName).toSet shouldBe Set("flask_sqlalchemy.py:.SQLAlchemy") + "app.py" + ).cpg + + "resolve correct imports via tag nodes" in { + val List(a: ResolvedTypeDecl, b: ResolvedMethod, c: UnknownImport, d: ResolvedMember) = + cpg.file(".*UserController.py").ast.isCall.where( + _.referencedImports + ).tag.toResolvedImport.toList: @unchecked + a.fullName shouldBe "app.py:" + b.fullName shouldBe "app.py:" + c.path shouldBe "flask.py:.jsonify" + d.basePath shouldBe "app.py:" + d.memberName shouldBe "db" + + val List(sqlAlchemyM: UnknownMethod, sqlAlchemyT: UnknownTypeDecl) = + cpg.file(".*app.py").ast.isCall.where( + _.referencedImports + ).tag.toResolvedImport.toList: @unchecked + sqlAlchemyM.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.__init__" + sqlAlchemyT.fullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy" + } + + "be determined as a variable reference and have its type recovered correctly" in { + cpg.identifier("db").map(_.typeFullName).toSet shouldBe Set( + "flask_sqlalchemy.py:.SQLAlchemy" + ) + + cpg + .call("add") + .where(_.parentBlock.ast.isIdentifier.typeFullName( + "flask_sqlalchemy.py:.SQLAlchemy" + )) + .where(_.parentBlock.ast.isFieldIdentifier.canonicalName("session")) + .headOption + .map(_.code) shouldBe Some("tmp0.add(user)") + } + + "provide a dummy type to a member if the member type is not known" in { + val Some(sessionTmpVar) = cpg.identifier("tmp0").headOption: @unchecked + sessionTmpVar.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.(session)" + + val Some(addCall) = cpg + .call("add") + .headOption: @unchecked + addCall.typeFullName shouldBe "ANY" + addCall.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.(session).add" + addCall.callee(NoResolve).isExternal.headOption shouldBe Some(true) + } - cpg - .call("add") - .where(_.parentBlock.ast.isIdentifier.typeFullName("flask_sqlalchemy.py:.SQLAlchemy")) - .where(_.parentBlock.ast.isFieldIdentifier.canonicalName("session")) - .headOption - .map(_.code) shouldBe Some("tmp0.add(user)") } - "provide a dummy type to a member if the member type is not known" in { - val Some(sessionTmpVar) = cpg.identifier("tmp0").headOption: @unchecked - sessionTmpVar.typeFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.(session)" - - val Some(addCall) = cpg - .call("add") - .headOption: @unchecked - addCall.typeFullName shouldBe "ANY" - addCall.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.(session).add" - addCall.callee(NoResolve).isExternal.headOption shouldBe Some(true) - } - - } - - "assignment from a call to a method inside an imported module" should { - lazy val cpg = code(""" + "assignment from a call to a method inside an imported module" should { + lazy val cpg = code(""" |import logging |log = logging.getLogger(__name__) |log.error("foo") |""".stripMargin).cpg - "resolve correct imports via tag nodes" in { - val List(logging: UnknownImport) = cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - logging.path shouldBe "logging.py:" + "resolve correct imports via tag nodes" in { + val List(logging: UnknownImport) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + logging.path shouldBe "logging.py:" + } + + "provide a dummy type" in { + val Some(log) = cpg.identifier("log").headOption: @unchecked + log.typeFullName shouldBe "logging.py:.getLogger." + val List(errorCall) = cpg.call("error").l + errorCall.methodFullName shouldBe "logging.py:.getLogger..error" + val List(getLoggerCall) = cpg.call("getLogger").l + getLoggerCall.methodFullName shouldBe "logging.py:.getLogger" + } } - "provide a dummy type" in { - val Some(log) = cpg.identifier("log").headOption: @unchecked - log.typeFullName shouldBe "logging.py:.getLogger." - val List(errorCall) = cpg.call("error").l - errorCall.methodFullName shouldBe "logging.py:.getLogger..error" - val List(getLoggerCall) = cpg.call("getLogger").l - getLoggerCall.methodFullName shouldBe "logging.py:.getLogger" - } - } - - "a constructor call from a field access of an externally imported package" should { - lazy val cpg = code(""" + "a constructor call from a field access of an externally imported package" should { + lazy val cpg = code(""" |import urllib.error |import urllib.request | |req = urllib.request.Request(url=apiUrl, data=dataBytes, method='POST') |""".stripMargin).cpg - "resolve correct imports via tag nodes" in { - val List(error: UnknownImport, request: UnknownImport) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - error.path shouldBe "urllib.py:.error" - request.path shouldBe "urllib.py:.request" + "resolve correct imports via tag nodes" in { + val List(error: UnknownImport, request: UnknownImport) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + error.path shouldBe "urllib.py:.error" + request.path shouldBe "urllib.py:.request" + } + + "reasonably determine the constructor type" in { + val Some(tmp0) = cpg.identifier("tmp0").headOption: @unchecked + tmp0.typeFullName shouldBe "urllib.py:.request" + val Some(requestCall) = cpg.call("Request").headOption: @unchecked + requestCall.methodFullName shouldBe "urllib.py:.request.Request.__init__" + } } - "reasonably determine the constructor type" in { - val Some(tmp0) = cpg.identifier("tmp0").headOption: @unchecked - tmp0.typeFullName shouldBe "urllib.py:.request" - val Some(requestCall) = cpg.call("Request").headOption: @unchecked - requestCall.methodFullName shouldBe "urllib.py:.request.Request.__init__" - } - } - - "a method call inherited from a super class should be recovered" should { - lazy val cpg = code( - """from pymongo import MongoClient + "a method call inherited from a super class should be recovered" should { + lazy val cpg = code( + """from pymongo import MongoClient |from django.conf import settings | | @@ -415,9 +440,9 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | def get_collection(self, name): | self.collection = self.db[name] |""".stripMargin, - "MongoConnection.py" - ).moreCode( - """ + "MongoConnection.py" + ).moreCode( + """ |from MongoConnection import MongoConnection | |class InstallationsDAO(MongoConnection): @@ -431,42 +456,52 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | return None | return dict(res).get("customerId", None) |""".stripMargin, - "InstallationDao.py" - ).moreCode( - """ + "InstallationDao.py" + ).moreCode( + """ |# dummy file to trigger isExternal = false on methods that are imported from here |""".stripMargin, - "pymongo.py" - ).cpg - - "resolve correct imports via tag nodes" in { - val List(a: ResolvedTypeDecl, b: ResolvedMethod, c: UnknownMethod, d: UnknownTypeDecl, e: UnknownImport) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - - a.fullName shouldBe "MongoConnection.py:.MongoConnection" - b.fullName shouldBe "MongoConnection.py:.MongoConnection.__init__" - c.fullName shouldBe "pymongo.py:.MongoClient.__init__" - d.fullName shouldBe "pymongo.py:.MongoClient" - e.path shouldBe Seq("django", "conf.py:.settings").mkString(File.separator) - } - - "recover a potential type for `self.collection` using the assignment at `get_collection` as a type hint" in { - val Some(selfFindFound) = cpg.typeDecl(".*InstallationsDAO.*").ast.isCall.name("find_one").headOption: @unchecked - selfFindFound.dynamicTypeHintFullName shouldBe Seq( - "__builtin.None.find_one", - "pymongo.py:.MongoClient.__init__...find_one" - ) - } - - "correctly determine that, despite being unable to resolve the correct method full name, that it is an internal method" in { - val Some(selfFindFound) = cpg.typeDecl(".*InstallationsDAO.*").ast.isCall.name("find_one").headOption: @unchecked - selfFindFound.callee.isExternal.toSeq shouldBe Seq(true, true, true) + "pymongo.py" + ).cpg + + "resolve correct imports via tag nodes" in { + val List( + a: ResolvedTypeDecl, + b: ResolvedMethod, + c: UnknownMethod, + d: UnknownTypeDecl, + e: UnknownImport + ) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + + a.fullName shouldBe "MongoConnection.py:.MongoConnection" + b.fullName shouldBe "MongoConnection.py:.MongoConnection.__init__" + c.fullName shouldBe "pymongo.py:.MongoClient.__init__" + d.fullName shouldBe "pymongo.py:.MongoClient" + e.path shouldBe Seq("django", "conf.py:.settings").mkString(File.separator) + } + + "recover a potential type for `self.collection` using the assignment at `get_collection` as a type hint" in { + val Some(selfFindFound) = cpg.typeDecl(".*InstallationsDAO.*").ast.isCall.name( + "find_one" + ).headOption: @unchecked + selfFindFound.dynamicTypeHintFullName shouldBe Seq( + "__builtin.None.find_one", + "pymongo.py:.MongoClient.__init__...find_one" + ) + } + + "correctly determine that, despite being unable to resolve the correct method full name, that it is an internal method" in { + val Some(selfFindFound) = cpg.typeDecl(".*InstallationsDAO.*").ast.isCall.name( + "find_one" + ).headOption: @unchecked + selfFindFound.callee.isExternal.toSeq shouldBe Seq(true, true, true) + } } - } - "a recursive field access based call type" should { - lazy val cpg = code( - """ + "a recursive field access based call type" should { + lazy val cpg = code( + """ |from flask import Flask, render_template |from flask_pymongo import PyMongo | @@ -481,18 +516,18 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | bikes = mongo.db.bikes.find() | return render_template("index.html", bikes=bikes) |""".stripMargin, - "app.py" - ) + "app.py" + ) - "manage to create a correct chain of dummy field accesses before the call" in { - val Some(bikeFind) = cpg.call.name("find").headOption: @unchecked - bikeFind.methodFullName shouldBe "flask_pymongo.py:.PyMongo.(db).(bikes).find" + "manage to create a correct chain of dummy field accesses before the call" in { + val Some(bikeFind) = cpg.call.name("find").headOption: @unchecked + bikeFind.methodFullName shouldBe "flask_pymongo.py:.PyMongo.(db).(bikes).find" + } } - } - "a call from an import where the import acts as a module" should { - lazy val cpg = code( - """import os + "a call from an import where the import acts as a module" should { + lazy val cpg = code( + """import os |import datadog | |# Initialize the Datadog client @@ -503,17 +538,17 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | |datadog.initialize(**options) |""".stripMargin, - "app.py" - ) + "app.py" + ) - "recover the import as an identifier and not directly as a call" in { - val Some(initCall) = cpg.call.name("initialize").headOption: @unchecked - initCall.methodFullName shouldBe "datadog.py:.initialize" + "recover the import as an identifier and not directly as a call" in { + val Some(initCall) = cpg.call.name("initialize").headOption: @unchecked + initCall.methodFullName shouldBe "datadog.py:.initialize" + } } - } - "a call made from within a call invocation" should { - lazy val cpg = code(""" + "a call made from within a call invocation" should { + lazy val cpg = code(""" |from __future__ import unicode_literals | |from django.db import migrations, models @@ -534,63 +569,82 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | ] |""".stripMargin) - "recover its full name successfully" in { - val Some(addFieldConstructor) = cpg.call.name("AddField").headOption: @unchecked - addFieldConstructor.methodFullName shouldBe Seq("django", "db.py:.migrations.AddField.__init__").mkString( - File.separator - ) - val Some(booleanFieldConstructor) = cpg.call.name("BooleanField").headOption: @unchecked - booleanFieldConstructor.methodFullName shouldBe Seq("django", "db.py:.models.BooleanField.__init__") - .mkString(File.separator) + "recover its full name successfully" in { + val Some(addFieldConstructor) = cpg.call.name("AddField").headOption: @unchecked + addFieldConstructor.methodFullName shouldBe Seq( + "django", + "db.py:.migrations.AddField.__init__" + ).mkString( + File.separator + ) + val Some(booleanFieldConstructor) = cpg.call.name("BooleanField").headOption: @unchecked + booleanFieldConstructor.methodFullName shouldBe Seq( + "django", + "db.py:.models.BooleanField.__init__" + ) + .mkString(File.separator) + } } - } - "a call made via an import from a directory" should { - lazy val cpg = code(""" + "a call made via an import from a directory" should { + lazy val cpg = code(""" |from data import db_session | |def foo(): | db_sess = db_session.create_session() | x = db_sess.query(foo, bar) |""".stripMargin) - .moreCode( - """ + .moreCode( + """ |from sqlalchemy.orm import Session | |def create_session() -> Session: | global __factory | return __factory() |""".stripMargin, - Seq("data", "db_session.py").mkString(File.separator) - ) - - "resolve correct imports via tag nodes" in { - val List( - sessionT: ResolvedTypeDecl, - sessionM: ResolvedMethod, - sqlSessionM: UnknownMethod, - sqlSessionT: UnknownTypeDecl - ) = cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - sessionT.fullName shouldBe Seq("data", "db_session.py:").mkString(File.separator) - sessionM.fullName shouldBe Seq("data", "db_session.py:").mkString(File.separator) - sqlSessionM.fullName shouldBe Seq("sqlalchemy", "orm.py:.Session.__init__").mkString(File.separator) - sqlSessionT.fullName shouldBe Seq("sqlalchemy", "orm.py:.Session").mkString(File.separator) + Seq("data", "db_session.py").mkString(File.separator) + ) + + "resolve correct imports via tag nodes" in { + val List( + sessionT: ResolvedTypeDecl, + sessionM: ResolvedMethod, + sqlSessionM: UnknownMethod, + sqlSessionT: UnknownTypeDecl + ) = cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + sessionT.fullName shouldBe Seq("data", "db_session.py:").mkString( + File.separator + ) + sessionM.fullName shouldBe Seq("data", "db_session.py:").mkString( + File.separator + ) + sqlSessionM.fullName shouldBe Seq( + "sqlalchemy", + "orm.py:.Session.__init__" + ).mkString(File.separator) + sqlSessionT.fullName shouldBe Seq("sqlalchemy", "orm.py:.Session").mkString( + File.separator + ) + } + + "recover its full name successfully" in { + val List(methodFullName) = cpg.call("query").methodFullName.l + methodFullName shouldBe Seq("sqlalchemy", "orm.py:.Session.query").mkString( + File.separator + ) + } + + "reflect these types as under the type full name" in { + val Some(ret) = cpg.method("create_session").methodReturn.headOption: @unchecked + ret.typeFullName shouldBe Seq("sqlalchemy", "orm.py:.Session").mkString( + File.separator + ) + } } - "recover its full name successfully" in { - val List(methodFullName) = cpg.call("query").methodFullName.l - methodFullName shouldBe Seq("sqlalchemy", "orm.py:.Session.query").mkString(File.separator) - } - - "reflect these types as under the type full name" in { - val Some(ret) = cpg.method("create_session").methodReturn.headOption: @unchecked - ret.typeFullName shouldBe Seq("sqlalchemy", "orm.py:.Session").mkString(File.separator) - } - } - - "recover a method ref from a field identifier" should { - lazy val cpg = code( - """ + "recover a method ref from a field identifier" should { + lazy val cpg = code( + """ |from django.conf.urls import url | |from student import views @@ -599,40 +653,46 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | url(r'^addStudent/$', views.add_student) |] |""".stripMargin, - "urls.py" - ).moreCode( - """ + "urls.py" + ).moreCode( + """ |def add_student(): | pass |""".stripMargin, - Seq("student", "views.py").mkString(File.separator) - ) - - "recover the method full name related" in { - val Some(methodRef) = cpg.methodRef.code("views.add_student").headOption: @unchecked - methodRef.methodFullName shouldBe Seq("student", "views.py:.add_student").mkString(File.separator) - methodRef.typeFullName shouldBe "" + Seq("student", "views.py").mkString(File.separator) + ) + + "recover the method full name related" in { + val Some(methodRef) = cpg.methodRef.code("views.add_student").headOption: @unchecked + methodRef.methodFullName shouldBe Seq( + "student", + "views.py:.add_student" + ).mkString(File.separator) + methodRef.typeFullName shouldBe "" + } } - } - "a type hint on a parameter" should { - lazy val cpg = code(""" + "a type hint on a parameter" should { + lazy val cpg = code(""" |import sqlalchemy.orm as orm | |async def get_user_by_email(email: str, db: orm.Session): | return db.query(user_models.User).filter(user_models.User.email == email).first() |""".stripMargin) - "be sufficient to resolve method full names at calls" in { - val List(call) = cpg.call("query").l - call.methodFullName shouldBe Seq("sqlalchemy", "orm.py:.Session.query").mkString(File.separator) - } + "be sufficient to resolve method full names at calls" in { + val List(call) = cpg.call("query").l + call.methodFullName shouldBe Seq( + "sqlalchemy", + "orm.py:.Session.query" + ).mkString(File.separator) + } - } + } - "recover a member call from a reference to an imported global variable" should { - lazy val cpg = code( - """from api import db + "recover a member call from a reference to an imported global variable" should { + lazy val cpg = code( + """from api import db | |class UserModel(db.Model): | @@ -644,34 +704,38 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | print(f"User with username={self.username} already exist") | db.session.rollback() |""".stripMargin, - Seq("api", "models", "user.py").mkString(File.separator) - ).moreCode( - """from flask_sqlalchemy import SQLAlchemy + Seq("api", "models", "user.py").mkString(File.separator) + ).moreCode( + """from flask_sqlalchemy import SQLAlchemy | |app = Flask(__name__, static_folder=Config.UPLOAD_FOLDER) | |db = SQLAlchemy(app) |""".stripMargin, - Seq("api", "__init__.py").mkString(File.separator) - ) - - "resolve correct imports via tag nodes" in { - val List(sqlSessionM: UnknownMethod, sqlSessionT: UnknownTypeDecl, db: ResolvedMember) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - sqlSessionM.fullName shouldBe Seq("flask_sqlalchemy.py:.SQLAlchemy.__init__").mkString(File.separator) - sqlSessionT.fullName shouldBe Seq("flask_sqlalchemy.py:.SQLAlchemy").mkString(File.separator) - db.basePath shouldBe Seq("api", "__init__.py:").mkString(File.separator) - db.memberName shouldBe "db" - } - - "recover a call to `add`" in { - val Some(addCall) = cpg.call("add").headOption: @unchecked - addCall.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.(session).add" + Seq("api", "__init__.py").mkString(File.separator) + ) + + "resolve correct imports via tag nodes" in { + val List(sqlSessionM: UnknownMethod, sqlSessionT: UnknownTypeDecl, db: ResolvedMember) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + sqlSessionM.fullName shouldBe Seq( + "flask_sqlalchemy.py:.SQLAlchemy.__init__" + ).mkString(File.separator) + sqlSessionT.fullName shouldBe Seq("flask_sqlalchemy.py:.SQLAlchemy").mkString( + File.separator + ) + db.basePath shouldBe Seq("api", "__init__.py:").mkString(File.separator) + db.memberName shouldBe "db" + } + + "recover a call to `add`" in { + val Some(addCall) = cpg.call("add").headOption: @unchecked + addCall.methodFullName shouldBe "flask_sqlalchemy.py:.SQLAlchemy.(session).add" + } } - } - "handle a wrapper function with the same name as an imported function" should { - lazy val cpg = code(""" + "handle a wrapper function with the same name as an imported function" should { + lazy val cpg = code(""" |import requests | |class Client: @@ -690,16 +754,16 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | return response |""".stripMargin) - "recover the child function `post` path correctly via receiver" in { - val Some(postCallReceiver) = cpg.identifier("requests").headOption: @unchecked - postCallReceiver.typeFullName shouldBe "requests.py:" - val Some(postCall) = cpg.call("post").headOption: @unchecked - postCall.methodFullName shouldBe "requests.py:.post" + "recover the child function `post` path correctly via receiver" in { + val Some(postCallReceiver) = cpg.identifier("requests").headOption: @unchecked + postCallReceiver.typeFullName shouldBe "requests.py:" + val Some(postCall) = cpg.call("post").headOption: @unchecked + postCall.methodFullName shouldBe "requests.py:.post" + } } - } - "handle a call from parameter with a type hint" should { - lazy val cpg = code(""" + "handle a call from parameter with a type hint" should { + lazy val cpg = code(""" |import models.user as user_models |import sqlalchemy.orm as orm | @@ -707,23 +771,31 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | return db.query(user_models.User).filter(user_models.User.email == email).first() |""".stripMargin) - "with the correct identifier and call types" in { - val Some(postCallReceiver) = cpg.identifier("db").headOption: @unchecked - postCallReceiver.typeFullName shouldBe Seq("sqlalchemy", "orm.py:.Session").mkString(File.separator) - val Some(postCall) = cpg.call("query").headOption: @unchecked - postCall.methodFullName shouldBe Seq("sqlalchemy", "orm.py:.Session.query").mkString(File.separator) + "with the correct identifier and call types" in { + val Some(postCallReceiver) = cpg.identifier("db").headOption: @unchecked + postCallReceiver.typeFullName shouldBe Seq( + "sqlalchemy", + "orm.py:.Session" + ).mkString(File.separator) + val Some(postCall) = cpg.call("query").headOption: @unchecked + postCall.methodFullName shouldBe Seq( + "sqlalchemy", + "orm.py:.Session.query" + ).mkString(File.separator) + } + + "reflect these types as under the type full name" in { + val List(p1, p2) = cpg.method("get_user_by_email").parameter.l + p1.typeFullName shouldBe "__builtin.str" + p2.typeFullName shouldBe Seq("sqlalchemy", "orm.py:.Session").mkString( + File.separator + ) + } } - "reflect these types as under the type full name" in { - val List(p1, p2) = cpg.method("get_user_by_email").parameter.l - p1.typeFullName shouldBe "__builtin.str" - p2.typeFullName shouldBe Seq("sqlalchemy", "orm.py:.Session").mkString(File.separator) - } - } - - "Import statement with method ref sample one" in { - val controller = - """ + "Import statement with method ref sample one" in { + val controller = + """ |from django.contrib import admin |from django.urls import path |from django.conf.urls import url @@ -733,23 +805,27 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | url(r'allPage', views.all_page) |] |""".stripMargin - val views = - """ + val views = + """ |def all_page(request): | print("All pages") |""".stripMargin - val cpg = code("print('Hello, world!')") - .moreCode(controller, Seq("controller", "urls.py").mkString(File.separator)) - .moreCode(views, Seq("student", "views.py").mkString(File.separator)) - - val Some(allPageRef) = cpg.call.methodFullName("django.*[.](path|url)").argument.isMethodRef.headOption: @unchecked - allPageRef.methodFullName shouldBe Seq("student", "views.py:.all_page").mkString(File.separator) - allPageRef.code shouldBe "views.all_page" - } - - "Import statement with method ref sample two" in { - val controller = - """ + val cpg = code("print('Hello, world!')") + .moreCode(controller, Seq("controller", "urls.py").mkString(File.separator)) + .moreCode(views, Seq("student", "views.py").mkString(File.separator)) + + val Some(allPageRef) = cpg.call.methodFullName( + "django.*[.](path|url)" + ).argument.isMethodRef.headOption: @unchecked + allPageRef.methodFullName shouldBe Seq("student", "views.py:.all_page").mkString( + File.separator + ) + allPageRef.code shouldBe "views.all_page" + } + + "Import statement with method ref sample two" in { + val controller = + """ |from django.contrib import admin |from django.urls import path |from django.conf.urls import url @@ -759,22 +835,24 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | url(r'allPage', views.all_page) |] |""".stripMargin - val views = - """ + val views = + """ |def all_page(request): | print("All pages") |""".stripMargin - val cpg = code(controller, "urls.py") - .moreCode(views, "views.py") - - val Some(allPageRef) = cpg.call.methodFullName("django.*[.](path|url)").argument.isMethodRef.headOption: @unchecked - allPageRef.methodFullName shouldBe "views.py:.all_page" - allPageRef.code shouldBe "views.all_page" - } + val cpg = code(controller, "urls.py") + .moreCode(views, "views.py") + + val Some(allPageRef) = cpg.call.methodFullName( + "django.*[.](path|url)" + ).argument.isMethodRef.headOption: @unchecked + allPageRef.methodFullName shouldBe "views.py:.all_page" + allPageRef.code shouldBe "views.all_page" + } - "Import statement with method ref sample three" in { - val controller = - """ + "Import statement with method ref sample three" in { + val controller = + """ |from django.contrib import admin |from django.urls import path |from django.conf.urls import url @@ -784,22 +862,26 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | url(r'allPage', views.all_page) |] |""".stripMargin - val views = - """ + val views = + """ |def all_page(request): | print("All pages") |""".stripMargin - val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) - .moreCode(views, Seq("controller", "views.py").mkString(File.separator)) - - val Some(allPageRef) = cpg.call.methodFullName("django.*[.](path|url)").argument.isMethodRef.headOption: @unchecked - allPageRef.methodFullName shouldBe Seq("controller", "views.py:.all_page").mkString(File.separator) - allPageRef.code shouldBe "views.all_page" - } + val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) + .moreCode(views, Seq("controller", "views.py").mkString(File.separator)) + + val Some(allPageRef) = cpg.call.methodFullName( + "django.*[.](path|url)" + ).argument.isMethodRef.headOption: @unchecked + allPageRef.methodFullName shouldBe Seq("controller", "views.py:.all_page").mkString( + File.separator + ) + allPageRef.code shouldBe "views.all_page" + } - "Import statement with method ref sample four" in { - val controller = - """ + "Import statement with method ref sample four" in { + val controller = + """ |from django.contrib import admin |from django.urls import path |from django.conf.urls import url @@ -809,22 +891,26 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | url(r'allPage', all_page) |] |""".stripMargin - val views = - """ + val views = + """ |def all_page(request): | print("All pages") |""".stripMargin - val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) - .moreCode(views, Seq("controller", "views.py").mkString(File.separator)) - - val Some(allPageRef) = cpg.call.methodFullName("django.*[.](path|url)").argument.isMethodRef.headOption: @unchecked - allPageRef.methodFullName shouldBe Seq("controller", "views.py:.all_page").mkString(File.separator) - allPageRef.code shouldBe "all_page" - } + val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) + .moreCode(views, Seq("controller", "views.py").mkString(File.separator)) + + val Some(allPageRef) = cpg.call.methodFullName( + "django.*[.](path|url)" + ).argument.isMethodRef.headOption: @unchecked + allPageRef.methodFullName shouldBe Seq("controller", "views.py:.all_page").mkString( + File.separator + ) + allPageRef.code shouldBe "all_page" + } - "Import statement with method ref sample five" in { - val controller = - """ + "Import statement with method ref sample five" in { + val controller = + """ |from django.contrib import admin |from django.urls import path |from django.conf.urls import url @@ -834,22 +920,26 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | url(r'allPage', all_page) |] |""".stripMargin - val views = - """ + val views = + """ |def all_page(request): | print("All pages") |""".stripMargin - val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) - .moreCode(views, Seq("student", "views.py").mkString(File.separator)) - - val Some(allPageRef) = cpg.call.methodFullName("django.*[.](path|url)").argument.isMethodRef.headOption: @unchecked - allPageRef.methodFullName shouldBe Seq("student", "views.py:.all_page").mkString(File.separator) - allPageRef.code shouldBe "all_page" - } + val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) + .moreCode(views, Seq("student", "views.py").mkString(File.separator)) + + val Some(allPageRef) = cpg.call.methodFullName( + "django.*[.](path|url)" + ).argument.isMethodRef.headOption: @unchecked + allPageRef.methodFullName shouldBe Seq("student", "views.py:.all_page").mkString( + File.separator + ) + allPageRef.code shouldBe "all_page" + } - "Import statement with method ref sample six" in { - val controller = - """ + "Import statement with method ref sample six" in { + val controller = + """ |from django.urls import path |from authy.views import PasswordChange | @@ -857,24 +947,29 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | path('changepassword/', PasswordChange, name='change_password') |] |""".stripMargin - val views = - """from django.contrib.auth.decorators import login_required + val views = + """from django.contrib.auth.decorators import login_required | |@login_required |def PasswordChange(request): | print("All pages") | |""".stripMargin - val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) - .moreCode(views, Seq("authy", "views.py").mkString(File.separator)) - - val Some(allPageRef) = cpg.call.methodFullName("django.*[.](path|url)").argument.isMethodRef.headOption: @unchecked - allPageRef.methodFullName shouldBe Seq("authy", "views.py:.PasswordChange").mkString(File.separator) - allPageRef.code shouldBe "PasswordChange" - } + val cpg = code(controller, Seq("controller", "urls.py").mkString(File.separator)) + .moreCode(views, Seq("authy", "views.py").mkString(File.separator)) + + val Some(allPageRef) = cpg.call.methodFullName( + "django.*[.](path|url)" + ).argument.isMethodRef.headOption: @unchecked + allPageRef.methodFullName shouldBe Seq( + "authy", + "views.py:.PasswordChange" + ).mkString(File.separator) + allPageRef.code shouldBe "PasswordChange" + } - "Classes extended by function calls" should { - lazy val cpg = code(""" + "Classes extended by function calls" should { + lazy val cpg = code(""" |from sqlalchemy.ext.declarative import declarative_base | |class Foo(declarative_base(metadata=metadata)): @@ -886,21 +981,29 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | |""".stripMargin) - "present an appropriate dummy type for direct call returns" in { - cpg.typeDecl("Foo").inheritsFromTypeFullName.l shouldBe List( - Seq("sqlalchemy", "ext", "declarative.py:.declarative_base.").mkString(File.separator) - ) + "present an appropriate dummy type for direct call returns" in { + cpg.typeDecl("Foo").inheritsFromTypeFullName.l shouldBe List( + Seq( + "sqlalchemy", + "ext", + "declarative.py:.declarative_base." + ).mkString(File.separator) + ) + } + + "present an appropriate dummy type for call results held by identifiers" in { + cpg.typeDecl("Bar").inheritsFromTypeFullName.l shouldBe List( + Seq( + "sqlalchemy", + "ext", + "declarative.py:.declarative_base." + ).mkString(File.separator) + ) + } } - "present an appropriate dummy type for call results held by identifiers" in { - cpg.typeDecl("Bar").inheritsFromTypeFullName.l shouldBe List( - Seq("sqlalchemy", "ext", "declarative.py:.declarative_base.").mkString(File.separator) - ) - } - } - - "Class methods with the `@classmethod` decorator" should { - lazy val cpg = code(""" + "Class methods with the `@classmethod` decorator" should { + lazy val cpg = code(""" |class MyClass: | | @classmethod @@ -909,51 +1012,57 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | |""".stripMargin) - "resolve the cls variable" in { - cpg.method("class_method").parameter.name("cls").typeFullName.headOption shouldBe Some( - "Test0.py:.MyClass" - ) + "resolve the cls variable" in { + cpg.method("class_method").parameter.name("cls").typeFullName.headOption shouldBe Some( + "Test0.py:.MyClass" + ) + } } - } - "calls from imported class fields" should { - lazy val cpg = code( - """ + "calls from imported class fields" should { + lazy val cpg = code( + """ |from .models import Profile | |def profile(request): | profile = Profile.objects.filter(user=request.user).order_by('-id')[0] |""".stripMargin, - "views.py" - ).moreCode( - """ + "views.py" + ).moreCode( + """ |from django.db import models | |class Profile(models.Model): | user = models.CharField(max_length=20) | name = models.CharField(max_length=50) |""".stripMargin, - "models.py" - ) - - "resolve correct imports via tag nodes" in { - val List(djangoModels: UnknownImport, profileT: ResolvedTypeDecl, profileM: ResolvedMethod) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - djangoModels.path shouldBe Seq("django", "db.py:.models").mkString(File.separator) - profileT.fullName shouldBe "models.py:.Profile" - profileM.fullName shouldBe "models.py:.Profile.__init__" - } + "models.py" + ) + + "resolve correct imports via tag nodes" in { + val List( + djangoModels: UnknownImport, + profileT: ResolvedTypeDecl, + profileM: ResolvedMethod + ) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + djangoModels.path shouldBe Seq("django", "db.py:.models").mkString( + File.separator + ) + profileT.fullName shouldBe "models.py:.Profile" + profileM.fullName shouldBe "models.py:.Profile.__init__" + } + + "resolve the `filter` call" in { + val Some(call) = cpg.call.nameExact("filter").headOption: @unchecked + call.methodFullName shouldBe "models.py:.Profile.(objects).filter" + } - "resolve the `filter` call" in { - val Some(call) = cpg.call.nameExact("filter").headOption: @unchecked - call.methodFullName shouldBe "models.py:.Profile.(objects).filter" } - } - - "Recovered values that are returned in methods" should { - lazy val cpg = code( - """ + "Recovered values that are returned in methods" should { + lazy val cpg = code( + """ |class Connector: | | botoClient = boto("s3") @@ -965,9 +1074,9 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | return self.botoClient | |""".stripMargin, - Seq("lib", "connector.py").mkString(File.separator) - ).moreCode( - """ + Seq("lib", "connector.py").mkString(File.separator) + ).moreCode( + """ |from lib.connector import Connector | |class Impl: @@ -975,31 +1084,41 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | c = Connector() | c.getBotoClient().getS3Object() |""".stripMargin, - "impl.py" - ) - - "resolve correct imports via tag nodes" in { - val List(connectorT: ResolvedTypeDecl, connectorM: ResolvedMethod) = - cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked - connectorT.fullName shouldBe Seq("lib", "connector.py:.Connector").mkString(File.separator) - connectorM.fullName shouldBe Seq("lib", "connector.py:.Connector.__init__").mkString(File.separator) - } - - "be able to use field accesses as type hints" in { - val Some(c) = cpg.identifier("c").headOption: @unchecked - c.typeFullName shouldBe Seq("lib", "connector.py:.Connector").mkString(File.separator) - val Some(getBotoClient) = cpg.call.nameExact("getBotoClient").headOption: @unchecked - getBotoClient.methodFullName shouldBe Seq("lib", "connector.py:.Connector.getBotoClient").mkString( - File.separator - ) - val Some(getS3Object) = cpg.call.nameExact("getS3Object").headOption: @unchecked - getS3Object.methodFullName shouldBe "boto..getS3Object" + "impl.py" + ) + + "resolve correct imports via tag nodes" in { + val List(connectorT: ResolvedTypeDecl, connectorM: ResolvedMethod) = + cpg.call.where(_.referencedImports).tag.toResolvedImport.toList: @unchecked + connectorT.fullName shouldBe Seq("lib", "connector.py:.Connector").mkString( + File.separator + ) + connectorM.fullName shouldBe Seq( + "lib", + "connector.py:.Connector.__init__" + ).mkString(File.separator) + } + + "be able to use field accesses as type hints" in { + val Some(c) = cpg.identifier("c").headOption: @unchecked + c.typeFullName shouldBe Seq("lib", "connector.py:.Connector").mkString( + File.separator + ) + val Some(getBotoClient) = cpg.call.nameExact("getBotoClient").headOption: @unchecked + getBotoClient.methodFullName shouldBe Seq( + "lib", + "connector.py:.Connector.getBotoClient" + ).mkString( + File.separator + ) + val Some(getS3Object) = cpg.call.nameExact("getS3Object").headOption: @unchecked + getS3Object.methodFullName shouldBe "boto..getS3Object" + } } - } - "Static class calls from imported types" should { - lazy val cpg = code( - """ + "Static class calls from imported types" should { + lazy val cpg = code( + """ |from db.redis import RedisDB | |class FooServer(): @@ -1010,9 +1129,9 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | redis = await RedisDB.instance().get_redis() | await redis.publish_json(123, {}) |""".stripMargin, - "fooserver.py" - ).moreCode( - """ + "fooserver.py" + ).moreCode( + """ |import aioredis | |class RedisDB(object): @@ -1033,34 +1152,44 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | redis = await self.get_redis() | await redis.set(key, value, expire=expires) |""".stripMargin, - Seq("db", "redis.py").mkString(File.separator) - ) - - "assert the method properties in RedisDB, especially quoted type hints" in { - val Some(redisDB) = cpg.typeDecl.nameExact("RedisDB").method.nameExact("").headOption: @unchecked - val List(instanceM, getRedisM, setM) = redisDB.astOut.isMethod.nameExact("instance", "get_redis", "set").l - - instanceM.methodReturn.typeFullName shouldBe Seq("db", "redis.py:.RedisDB").mkString(File.separator) - getRedisM.methodReturn.typeFullName shouldBe "aioredis.py:.Redis" - setM.methodReturn.typeFullName shouldBe "ANY" - } - - "be able to generate an appropriate dummy value" in { - val Some(redisSet) = cpg.call.code(".*set.*apiuserscache.*").headOption: @unchecked - redisSet.methodFullName shouldBe Seq("db", "redis.py:.RedisDB.set").mkString(File.separator) - } - - "be able to handle a simple call off an alias" in { - val Some(redisGet) = cpg.call.nameExact("publish_json").headOption: @unchecked - redisGet.methodFullName shouldBe Seq("db", "redis.py:.RedisDB.get_redis.publish_json").mkString( - File.separator - ) + Seq("db", "redis.py").mkString(File.separator) + ) + + "assert the method properties in RedisDB, especially quoted type hints" in { + val Some(redisDB) = + cpg.typeDecl.nameExact("RedisDB").method.nameExact("").headOption: @unchecked + val List(instanceM, getRedisM, setM) = + redisDB.astOut.isMethod.nameExact("instance", "get_redis", "set").l + + instanceM.methodReturn.typeFullName shouldBe Seq( + "db", + "redis.py:.RedisDB" + ).mkString(File.separator) + getRedisM.methodReturn.typeFullName shouldBe "aioredis.py:.Redis" + setM.methodReturn.typeFullName shouldBe "ANY" + } + + "be able to generate an appropriate dummy value" in { + val Some(redisSet) = cpg.call.code(".*set.*apiuserscache.*").headOption: @unchecked + redisSet.methodFullName shouldBe Seq("db", "redis.py:.RedisDB.set").mkString( + File.separator + ) + } + + "be able to handle a simple call off an alias" in { + val Some(redisGet) = cpg.call.nameExact("publish_json").headOption: @unchecked + redisGet.methodFullName shouldBe Seq( + "db", + "redis.py:.RedisDB.get_redis.publish_json" + ).mkString( + File.separator + ) + } } - } - "Type instantiation via caller" should { - lazy val cpg = code( - """ + "Type instantiation via caller" should { + lazy val cpg = code( + """ |from oauth2 import Token | |class FlickrAuth(ConsumerBasedOAuth): @@ -1068,10 +1197,10 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | response = self.fetch_response(request) | token = Token.from_string(response) |""".stripMargin, - Seq("social_auth", "backends", "contrib", "flickr.py").mkString(File.separator) - ) - .moreCode( - """ + Seq("social_auth", "backends", "contrib", "flickr.py").mkString(File.separator) + ) + .moreCode( + """ |class Token(object): | | key = None @@ -1104,27 +1233,36 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | token = Token(key, secret) | return token |""".stripMargin, - Seq("oauth2", "__init__.py").mkString(File.separator) - ) - - "instantiate the return value correctly under `from_string`" in { - val Some(token) = cpg.method("from_string").ast.isIdentifier.nameExact("token").headOption: @unchecked - token.typeFullName shouldBe Seq("oauth2", "__init__.py:.Token").mkString(File.separator) - val Some(fromString) = cpg.method("from_string").methodReturn.headOption: @unchecked - fromString.typeFullName shouldBe Seq("oauth2", "__init__.py:.Token").mkString(File.separator) - } + Seq("oauth2", "__init__.py").mkString(File.separator) + ) + + "instantiate the return value correctly under `from_string`" in { + val Some(token) = + cpg.method("from_string").ast.isIdentifier.nameExact("token").headOption: @unchecked + token.typeFullName shouldBe Seq("oauth2", "__init__.py:.Token").mkString( + File.separator + ) + val Some(fromString) = cpg.method("from_string").methodReturn.headOption: @unchecked + fromString.typeFullName shouldBe Seq("oauth2", "__init__.py:.Token").mkString( + File.separator + ) + } + + "propagate the type in the return value" in { + val Some(token) = cpg.method("access_token").ast.isIdentifier.nameExact( + "token" + ).headOption: @unchecked + token.typeFullName shouldBe Seq("oauth2", "__init__.py:.Token").mkString( + File.separator + ) + } - "propagate the type in the return value" in { - val Some(token) = cpg.method("access_token").ast.isIdentifier.nameExact("token").headOption: @unchecked - token.typeFullName shouldBe Seq("oauth2", "__init__.py:.Token").mkString(File.separator) } - } - - "Imports from two module neighbours" should { + "Imports from two module neighbours" should { - lazy val cpg = code( - """ + lazy val cpg = code( + """ |from fastapi import FastAPI |import itemsrouter |app = FastAPI() @@ -1135,9 +1273,9 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | responses={404: {"description": "Not found"}}, |) |""".stripMargin, - Seq("code", "main.py").mkString(File.separator) - ).moreCode( - """ + Seq("code", "main.py").mkString(File.separator) + ).moreCode( + """ |from fastapi import APIRouter | |router = APIRouter() @@ -1147,21 +1285,184 @@ class TypeRecoveryPassTests extends PySrc2CpgFixture(withOssDataflow = false) { | return fake_items_db | |""".stripMargin, - Seq("code", "itemsrouter.py").mkString(File.separator) - ) - - "preserve the filename path relative to the root and not themselves" in { - val itemsrouter = cpg.identifier.where(_.typeFullName(".*itemsrouter.py:")).l - itemsrouter.forall( - _.typeFullName == Seq("code", "itemsrouter.py:").mkString(File.separator) - ) shouldBe true + Seq("code", "itemsrouter.py").mkString(File.separator) + ) + + "preserve the filename path relative to the root and not themselves" in { + val itemsrouter = cpg.identifier.where(_.typeFullName(".*itemsrouter.py:")).l + itemsrouter.forall( + _.typeFullName == Seq("code", "itemsrouter.py:").mkString(File.separator) + ) shouldBe true + } + + "correctly infer the `fastapi` types" in { + cpg.identifier("fastapi").forall( + _.typeFullName == "fastapi.py:.APIRouter" + ) shouldBe true + cpg.identifier("app").forall( + _.typeFullName == "fastapi.py:.FastAPI" + ) shouldBe true + } + } - "correctly infer the `fastapi` types" in { - cpg.identifier("fastapi").forall(_.typeFullName == "fastapi.py:.APIRouter") shouldBe true - cpg.identifier("app").forall(_.typeFullName == "fastapi.py:.FastAPI") shouldBe true + "assignment to non-chained index access of an imported member method call" should { + val cpg = code( + """ + |import foo + |x = foo.bar() + |y = x[0] + |""".stripMargin + ) + + "provide meaningful typeFullName for the target of the first assignment" in { + cpg.assignment.target.isIdentifier.name("x").l match + case List(x) => x.typeFullName shouldBe "foo.py:.bar." + case result => fail(s"Expected single assignment to x, but got $result") + } + + "provide meaningful typeFullName for the target of the second assignment" in { + cpg.assignment.target.isIdentifier.name("y").l match + case List(y) => + y.typeFullName shouldBe "foo.py:.bar.." + case result => fail(s"Expected single assignment to y, but got $result") + } } - } + "assignment to chained index access of an imported member method call" should { + val cpg = code( + """ + |import foo + |y = foo.bar()[0] + |""".stripMargin + ) + + "provide meaningful typeFullName for the target of the assignment" in { + cpg.assignment.target.isIdentifier.name("y").l match + case List(y) => y.typeFullName shouldBe "foo.py:.bar." + case result => fail(s"Expected single assignment to y, but got $result") + } + } + + "assignment to interspersed index access with imported method calls" should { + val cpg = code( + """ + |import foo + |x = foo.bar()[0].baz() + |""".stripMargin + ) + + "have correct methodFullName for `bar`" in { + cpg.call.name("bar").l match + case List(bar) => bar.methodFullName shouldBe "foo.py:.bar" + case result => fail(s"Expected single call to bar, but got $result") + } + + "have correct methodFullName for `baz`" in { + cpg.call.name("baz").l match + case List(baz) => + baz.methodFullName shouldBe "foo.py:.bar..baz" + case result => fail(s"Expected single call to baz, but got $result") + } + + // TODO: Missing the last . Needs to take into account the lowering of consecutive + // field accesses into three-address code blocks. + "provide meaningful typeFullName for the target of the assignment" ignore { + cpg.assignment.target.isIdentifier.name("x").l match + case List(x) => x.typeFullName shouldBe "foo.py:.bar..baz" + case result => fail(s"Expected single assignment to x, but got $result") + } + } -} + "call to interspersed index access with imported method calls and constructors" should { + val cpg = code( + """ + |import boto3 + |sqs = boto3.resource('sqs') + |queue = sqs.Queue('url') + |queue.receive_messages()[0].delete() + |""".stripMargin + ) + + "have correct methodFullName for `delete`" in { + cpg.call.name("delete").l match + case List(delete) => + delete.methodFullName shouldBe "boto3.py:.resource..Queue.receive_messages..delete" + case result => fail(s"Expected single call to delete, but got $result") + } + + "have correct methodFullName for `resource`" in { + cpg.call.name("resource").l match + case List(resource) => resource.methodFullName shouldBe "boto3.py:.resource" + case result => fail(s"Expected single call to resource, but got $result") + } + + "have correct methodFullName for `receive_messages`" in { + cpg.call.name("receive_messages").l match + case List(recv) => + recv.methodFullName shouldBe "boto3.py:.resource..Queue.receive_messages" + case result => fail(s"Expected single call to receive_messages, but got $result") + } + + "provide meaningful typeFullName for `sqs`" in { + cpg.assignment.target.isIdentifier.name("sqs").l match + case List(sqs) => + sqs.typeFullName shouldBe "boto3.py:.resource." + case result => fail(s"Expected single assignment to sqs, but got $result") + } + + "provide meaningful typeFullName for `queue`" in { + cpg.assignment.target.isIdentifier.name("queue").l match + case List(queue) => + queue.typeFullName shouldBe "boto3.py:.resource..Queue" + case result => fail(s"Expected single assignment to queue, but got $result") + } + } + + "`__builtin.print` call with an external non-imported call result variable for argument" should { + val cpg = code( + """ + |a = foo(10) + |print(a) + |""".stripMargin + ) + "have correct methodFullName for `print`" in { + cpg.call("print").l match + case List(printCall) => printCall.methodFullName shouldBe "__builtin.print" + case result => fail(s"Expected single print call but got $result") + } + "have correct methodFullName for `foo`" in { + cpg.call("foo").l match + case List(fooCall) => fooCall.methodFullName shouldBe "foo" + case result => fail(s"Expected single foo call but got $result") + } + + "provide meaningful typeFullName for the target of assignment" in { + cpg.assignment.target.isIdentifier.name("a").l match + case List(a) => a.typeFullName shouldBe "foo." + case result => fail(s"Expected single assignment to a, but got $result") + } + } + + "assignment to non imported call with int variable for argument" should { + val cpg = code( + """ + |a = 10 + |b = foo(a) + |""".stripMargin) + + "have correct methodFullName for `foo`" in { + cpg.call("foo").l match { + case List(fooCall) => fooCall.methodFullName shouldBe "__builtin.int.foo" + case result => fail(s"Expected single foo call but got $result") + } + } + + "provide meaningful typeFullName for the target of the assignment" in { + cpg.assignment.target.isIdentifier.name("b").l match { + case List(b) => b.typeFullName shouldBe "foo." + case result => fail(s"Expected single assignment to b, but got $result") + } + } + } +end TypeRecoveryPassTests diff --git a/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/frontend/XTypeRecovery.scala b/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/frontend/XTypeRecovery.scala index 8d911865..18406b20 100644 --- a/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/frontend/XTypeRecovery.scala +++ b/platform/frontends/x2cpg/src/main/scala/io/appthreat/x2cpg/passes/frontend/XTypeRecovery.scala @@ -422,7 +422,7 @@ abstract class RecoverForXCompilationUnit[CompilationUnitType <: AstNode]( visitIdentifierAssignedToConstructor(i, c) else if symbolTable.contains(c) then visitIdentifierAssignedToCallRetVal(i, c) - else if c.argument.headOption.exists(symbolTable.contains) then + else if c.argument.argumentIndex(0).headOption.exists(symbolTable.contains) then setCallMethodFullNameFromBase(c) // Repeat this method now that the call has a type visitIdentifierAssignedToCall(i, c) diff --git a/platform/src/universal/schema-extender/build.sbt b/platform/src/universal/schema-extender/build.sbt index 6566e7d3..be40d153 100644 --- a/platform/src/universal/schema-extender/build.sbt +++ b/platform/src/universal/schema-extender/build.sbt @@ -1,6 +1,6 @@ name := "schema-extender" -ThisBuild / scalaVersion := "3.4.1" +ThisBuild / scalaVersion := "3.4.2" val cpgVersion = IO.read(file("cpg-version")) diff --git a/poetry.lock b/poetry.lock index 84d69182..6a1d0482 100644 --- a/poetry.lock +++ b/poetry.lock @@ -404,13 +404,13 @@ css = ["tinycss2 (>=1.1.0,<1.3)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -683,63 +683,63 @@ test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] [[package]] name = "coverage" -version = "7.5.3" +version = "7.5.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, + {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, + {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, + {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, + {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, + {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, + {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, + {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, + {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, + {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, + {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, + {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, + {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, ] [package.dependencies] @@ -765,33 +765,33 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "debugpy" -version = "1.8.1" +version = "1.8.2" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741"}, - {file = "debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e"}, - {file = "debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0"}, - {file = "debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd"}, - {file = "debugpy-1.8.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb"}, - {file = "debugpy-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"}, - {file = "debugpy-1.8.1-cp311-cp311-win32.whl", hash = "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146"}, - {file = "debugpy-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8"}, - {file = "debugpy-1.8.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539"}, - {file = "debugpy-1.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace"}, - {file = "debugpy-1.8.1-cp312-cp312-win32.whl", hash = "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0"}, - {file = "debugpy-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98"}, - {file = "debugpy-1.8.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39"}, - {file = "debugpy-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7"}, - {file = "debugpy-1.8.1-cp38-cp38-win32.whl", hash = "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9"}, - {file = "debugpy-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234"}, - {file = "debugpy-1.8.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42"}, - {file = "debugpy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703"}, - {file = "debugpy-1.8.1-cp39-cp39-win32.whl", hash = "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23"}, - {file = "debugpy-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3"}, - {file = "debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242"}, - {file = "debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42"}, + {file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"}, + {file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"}, + {file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"}, + {file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"}, + {file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"}, + {file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"}, + {file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"}, + {file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"}, + {file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"}, + {file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"}, + {file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"}, + {file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"}, + {file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"}, + {file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"}, + {file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"}, + {file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"}, + {file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"}, + {file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"}, + {file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"}, + {file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"}, + {file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"}, + {file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"}, ] [[package]] @@ -846,13 +846,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "fastjsonschema" -version = "2.19.1" +version = "2.20.0" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" files = [ - {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"}, - {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"}, + {file = "fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"}, + {file = "fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23"}, ] [package.extras] @@ -860,85 +860,85 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.14.0" +version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] name = "flake8" -version = "7.0.0" +version = "7.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, + {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, + {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" +pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "fonttools" -version = "4.52.4" +version = "4.53.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.52.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb8cd6559f0ae3a8f5e146f80ab2a90ad0325a759be8d48ee82758a0b89fa0aa"}, - {file = "fonttools-4.52.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ecb88318ff249bd2a715e7aec36774ce7ae3441128007ef72a39a60601f4a8f"}, - {file = "fonttools-4.52.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a22cf1adaae7b2ba2ed7d8651a4193a4f348744925b4b740e6b38a94599c5b"}, - {file = "fonttools-4.52.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8873d6edd1dae5c088dd3d61c9fd4dd80c827c486fa224d368233e7f33dc98af"}, - {file = "fonttools-4.52.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73ba38b98c012957940a04d9eb5439b42565ac892bba8cfc32e10d88e73921fe"}, - {file = "fonttools-4.52.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9725687db3c1cef13c0f40b380c3c15bea0113f4d0231b204d58edd5f2a53d90"}, - {file = "fonttools-4.52.4-cp310-cp310-win32.whl", hash = "sha256:9180775c9535389a665cae7c5282f8e07754beabf59b66aeba7f6bfeb32a3652"}, - {file = "fonttools-4.52.4-cp310-cp310-win_amd64.whl", hash = "sha256:46cc5d06ee05fd239c45d7935aaffd060ee773a88b97e901df50478247472643"}, - {file = "fonttools-4.52.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d272c7e173c3085308345ccc7fb2ad6ce7f415d777791dd6ce4e8140e354d09c"}, - {file = "fonttools-4.52.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21921e5855c399d10ddfc373538b425cabcf8b3258720b51450909e108896450"}, - {file = "fonttools-4.52.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f6001814ec5e0c961cabe89642f7e8d7e07892b565057aa526569b9ebb711c"}, - {file = "fonttools-4.52.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b0b9eb0f55dce9c7278ad4175f1cbaed23b799dce5ecc20e3213da241584140"}, - {file = "fonttools-4.52.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:70d87f2099006304d33438bdaa5101953b7e22e23a93b1c7b7ed0f32ff44b423"}, - {file = "fonttools-4.52.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e176249292eccd89f81d39f514f2b5e8c75dfc9cef8653bdc3021d06697e9eff"}, - {file = "fonttools-4.52.4-cp311-cp311-win32.whl", hash = "sha256:bb7d206fa5ba6e082ba5d5e1b7107731029fc3a55c71c48de65121710d817986"}, - {file = "fonttools-4.52.4-cp311-cp311-win_amd64.whl", hash = "sha256:346d08ff92e577b2dc5a0c228487667d23fe2da35a8b9a8bba22c2b6ba8be21c"}, - {file = "fonttools-4.52.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d2cc7906bc0afdd2689aaf88b910307333b1f936262d1d98f25dbf8a5eb2e829"}, - {file = "fonttools-4.52.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00d9abf4b400f98fb895566eb298f60432b4b29048e3dc02807427b09a06604e"}, - {file = "fonttools-4.52.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b419207e53db1599b3d385afd4bca6692c219d53732890d0814a2593104d0e2"}, - {file = "fonttools-4.52.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf694159528022daa71b1777cb6ec9e0ebbdd29859f3e9c845826cafaef4ca29"}, - {file = "fonttools-4.52.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a5d1b0475050056d2e3bc378014f2ea2230e8ae434eeac8dfb182aa8efaf642"}, - {file = "fonttools-4.52.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c3ad89204c2d7f419436f1d6fde681b070c5e20b888beb57ccf92f640628cc9"}, - {file = "fonttools-4.52.4-cp312-cp312-win32.whl", hash = "sha256:1dc626de4b204d025d029e646bae8fdbf5acd9217158283a567f4b523fda3bae"}, - {file = "fonttools-4.52.4-cp312-cp312-win_amd64.whl", hash = "sha256:309b617942041073ffa96090d320b99d75648ed16e0c67fb1aa7788e06c834de"}, - {file = "fonttools-4.52.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8b186cd6b8844f6cf04a7e0a174bc3649d3deddbfc10dc59846a4381f796d348"}, - {file = "fonttools-4.52.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9ed23a03b7d9f0e29ca0679eafe5152aeccb0580312a3fc36f0662e178b4791b"}, - {file = "fonttools-4.52.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b53386214197bd5b3e3c753895bad691de84726ced3c222a59cde1dd12d57b"}, - {file = "fonttools-4.52.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7467161f1eed557dbcec152d5ee95540200b1935709fa73307da16bc0b7ca361"}, - {file = "fonttools-4.52.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b4cba644e2515d685d4ee3ca2fbb5d53930a0e9ec2cf332ed704dc341b145878"}, - {file = "fonttools-4.52.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:890e7a657574610330e42dd1e38d3b9e0a8cb0eff3da080f80995460a256d3dd"}, - {file = "fonttools-4.52.4-cp38-cp38-win32.whl", hash = "sha256:7dccf4666f716e5e0753f0fa28dad2f4431154c87747bc781c838b8a5dca990e"}, - {file = "fonttools-4.52.4-cp38-cp38-win_amd64.whl", hash = "sha256:a791f002d1b717268235cfae7e4957b7fd132e92e2c5400e521bf191f1b3a9a5"}, - {file = "fonttools-4.52.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:05e4291db6af66f466a203d9922e4c1d3e18ef16868f76f10b00e2c3b9814df2"}, - {file = "fonttools-4.52.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a64e72d2c144630e017ac9c1c416ddf8ac43bef9a083bf81fe08c0695f0baa95"}, - {file = "fonttools-4.52.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebb183ed8b789cece0bd6363121913fb6da4034af89a2fa5408e42a1592889a8"}, - {file = "fonttools-4.52.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4daf2751a98c69d9620717826ed6c5743b662ef0ae7bb33dc6c205425e48eba"}, - {file = "fonttools-4.52.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:15efb2ba4b8c2d012ee0bb7a850c2e4780c530cc83ec8e843b2a97f8b3a5fd4b"}, - {file = "fonttools-4.52.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:35af630404223273f1d7acd4761f399131c62820366f53eac029337069f5826a"}, - {file = "fonttools-4.52.4-cp39-cp39-win32.whl", hash = "sha256:d0184aa88865339d96f7f452e8c5b621186ef7638744d78bf9b775d67e206819"}, - {file = "fonttools-4.52.4-cp39-cp39-win_amd64.whl", hash = "sha256:e03dae26084bb3632b4a77b1cd0419159d2226911aff6dc4c7e3058df68648c6"}, - {file = "fonttools-4.52.4-py3-none-any.whl", hash = "sha256:95e8a5975d08d0b624a14eec0f987e204ad81b480e24c5436af99170054434b8"}, - {file = "fonttools-4.52.4.tar.gz", hash = "sha256:859399b7adc8ac067be8e5c80ef4bb2faddff97e9b40896a9de75606a43d0469"}, + {file = "fonttools-4.53.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:52a6e0a7a0bf611c19bc8ec8f7592bdae79c8296c70eb05917fd831354699b20"}, + {file = "fonttools-4.53.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e40013572bfb843d6794a3ce076c29ef4efd15937ab833f520117f8eccc84fd6"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715b41c3e231f7334cbe79dfc698213dcb7211520ec7a3bc2ba20c8515e8a3b5"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74ae2441731a05b44d5988d3ac2cf784d3ee0a535dbed257cbfff4be8bb49eb9"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:95db0c6581a54b47c30860d013977b8a14febc206c8b5ff562f9fe32738a8aca"}, + {file = "fonttools-4.53.0-cp310-cp310-win32.whl", hash = "sha256:9cd7a6beec6495d1dffb1033d50a3f82dfece23e9eb3c20cd3c2444d27514068"}, + {file = "fonttools-4.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:daaef7390e632283051e3cf3e16aff2b68b247e99aea916f64e578c0449c9c68"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a209d2e624ba492df4f3bfad5996d1f76f03069c6133c60cd04f9a9e715595ec"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f520d9ac5b938e6494f58a25c77564beca7d0199ecf726e1bd3d56872c59749"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eceef49f457253000e6a2d0f7bd08ff4e9fe96ec4ffce2dbcb32e34d9c1b8161"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1f3e34373aa16045484b4d9d352d4c6b5f9f77ac77a178252ccbc851e8b2ee"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:28d072169fe8275fb1a0d35e3233f6df36a7e8474e56cb790a7258ad822b6fd6"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a2a6ba400d386e904fd05db81f73bee0008af37799a7586deaa4aef8cd5971e"}, + {file = "fonttools-4.53.0-cp311-cp311-win32.whl", hash = "sha256:bb7273789f69b565d88e97e9e1da602b4ee7ba733caf35a6c2affd4334d4f005"}, + {file = "fonttools-4.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:9fe9096a60113e1d755e9e6bda15ef7e03391ee0554d22829aa506cdf946f796"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d8f191a17369bd53a5557a5ee4bab91d5330ca3aefcdf17fab9a497b0e7cff7a"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93156dd7f90ae0a1b0e8871032a07ef3178f553f0c70c386025a808f3a63b1f4"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bff98816cb144fb7b85e4b5ba3888a33b56ecef075b0e95b95bcd0a5fbf20f06"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:973d030180eca8255b1bce6ffc09ef38a05dcec0e8320cc9b7bcaa65346f341d"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4ee5a24e281fbd8261c6ab29faa7fd9a87a12e8c0eed485b705236c65999109"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5bc124fae781a4422f61b98d1d7faa47985f663a64770b78f13d2c072410c2"}, + {file = "fonttools-4.53.0-cp312-cp312-win32.whl", hash = "sha256:a239afa1126b6a619130909c8404070e2b473dd2b7fc4aacacd2e763f8597fea"}, + {file = "fonttools-4.53.0-cp312-cp312-win_amd64.whl", hash = "sha256:45b4afb069039f0366a43a5d454bc54eea942bfb66b3fc3e9a2c07ef4d617380"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:93bc9e5aaa06ff928d751dc6be889ff3e7d2aa393ab873bc7f6396a99f6fbb12"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2367d47816cc9783a28645bc1dac07f8ffc93e0f015e8c9fc674a5b76a6da6e4"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:907fa0b662dd8fc1d7c661b90782ce81afb510fc4b7aa6ae7304d6c094b27bce"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e0ad3c6ea4bd6a289d958a1eb922767233f00982cf0fe42b177657c86c80a8f"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:73121a9b7ff93ada888aaee3985a88495489cc027894458cb1a736660bdfb206"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ee595d7ba9bba130b2bec555a40aafa60c26ce68ed0cf509983e0f12d88674fd"}, + {file = "fonttools-4.53.0-cp38-cp38-win32.whl", hash = "sha256:fca66d9ff2ac89b03f5aa17e0b21a97c21f3491c46b583bb131eb32c7bab33af"}, + {file = "fonttools-4.53.0-cp38-cp38-win_amd64.whl", hash = "sha256:31f0e3147375002aae30696dd1dc596636abbd22fca09d2e730ecde0baad1d6b"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d6166192dcd925c78a91d599b48960e0a46fe565391c79fe6de481ac44d20ac"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef50ec31649fbc3acf6afd261ed89d09eb909b97cc289d80476166df8438524d"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f193f060391a455920d61684a70017ef5284ccbe6023bb056e15e5ac3de11d1"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a4788036201c908079e89ae3f5399b33bf45b9ea4514913f4dbbe4fac08efe0"}, + {file = "fonttools-4.53.0-cp39-cp39-win32.whl", hash = "sha256:d1a24f51a3305362b94681120c508758a88f207fa0a681c16b5a4172e9e6c7a9"}, + {file = "fonttools-4.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:1e677bfb2b4bd0e5e99e0f7283e65e47a9814b0486cb64a41adf9ef110e078f2"}, + {file = "fonttools-4.53.0-py3-none-any.whl", hash = "sha256:6b4f04b1fbc01a3569d63359f2227c89ab294550de277fd09d8fca6185669fa4"}, + {file = "fonttools-4.53.0.tar.gz", hash = "sha256:c93ed66d32de1559b6fc348838c7572d5c0ac1e4a258e76763a5caddd8944002"}, ] [package.extras] @@ -1054,13 +1054,13 @@ files = [ [[package]] name = "fsspec" -version = "2024.5.0" +version = "2024.6.1" description = "File-system specification" optional = false python-versions = ">=3.8" files = [ - {file = "fsspec-2024.5.0-py3-none-any.whl", hash = "sha256:e0fdbc446d67e182f49a70b82cf7889028a63588fde6b222521f10937b2b670c"}, - {file = "fsspec-2024.5.0.tar.gz", hash = "sha256:1d021b0b0f933e3b3029ed808eb400c08ba101ca2de4b3483fbc9ca23fcee94a"}, + {file = "fsspec-2024.6.1-py3-none-any.whl", hash = "sha256:3cb443f8bcd2efb31295a5b9fdb02aee81d8452c80d28f97a6d0959e6cee101e"}, + {file = "fsspec-2024.6.1.tar.gz", hash = "sha256:fad7d7e209dd4c1208e3bbfda706620e0da5142bebbd9c384afb95b07e798e49"}, ] [package.extras] @@ -1069,6 +1069,7 @@ adl = ["adlfs"] arrow = ["pyarrow (>=1)"] dask = ["dask", "distributed"] dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] dropbox = ["dropbox", "dropboxdrivefs", "requests"] full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] fuse = ["fusepy"] @@ -1216,13 +1217,13 @@ files = [ [[package]] name = "ipykernel" -version = "6.29.4" +version = "6.29.5" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" files = [ - {file = "ipykernel-6.29.4-py3-none-any.whl", hash = "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da"}, - {file = "ipykernel-6.29.4.tar.gz", hash = "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c"}, + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, ] [package.dependencies] @@ -1249,13 +1250,13 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio [[package]] name = "ipython" -version = "8.24.0" +version = "8.26.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.24.0-py3-none-any.whl", hash = "sha256:d7bf2f6c4314984e3e02393213bab8703cf163ede39672ce5918c51fe253a2a3"}, - {file = "ipython-8.24.0.tar.gz", hash = "sha256:010db3f8a728a578bb641fdd06c063b9fb8e96a9464c63aec6310fbcb5e80501"}, + {file = "ipython-8.26.0-py3-none-any.whl", hash = "sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff"}, + {file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"}, ] [package.dependencies] @@ -1274,7 +1275,7 @@ typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "stack-data", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] kernel = ["ipykernel"] matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] @@ -1282,7 +1283,7 @@ nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] @@ -1359,13 +1360,13 @@ files = [ [[package]] name = "jsonpointer" -version = "2.4" +version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +python-versions = ">=3.7" files = [ - {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, - {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] [[package]] @@ -1494,13 +1495,13 @@ jupyter-server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.14.0" +version = "2.14.1" description = "The backendā€”i.e. core services, APIs, and REST endpointsā€”to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.14.0-py3-none-any.whl", hash = "sha256:fb6be52c713e80e004fac34b35a0990d6d36ba06fd0a2b2ed82b899143a64210"}, - {file = "jupyter_server-2.14.0.tar.gz", hash = "sha256:659154cea512083434fd7c93b7fe0897af7a2fd0b9dd4749282b42eaac4ae677"}, + {file = "jupyter_server-2.14.1-py3-none-any.whl", hash = "sha256:16f7177c3a4ea8fe37784e2d31271981a812f0b2874af17339031dc3510cc2a5"}, + {file = "jupyter_server-2.14.1.tar.gz", hash = "sha256:12558d158ec7a0653bf96cc272bc7ad79e0127d503b982ed144399346694f726"}, ] [package.dependencies] @@ -1525,7 +1526,7 @@ traitlets = ">=5.6.0" websocket-client = ">=1.7" [package.extras] -docs = ["ipykernel", "jinja2", "jupyter-client", "jupyter-server", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] +docs = ["ipykernel", "jinja2", "jupyter-client", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0,<9)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.7)", "pytest-timeout", "requests"] [[package]] @@ -1549,13 +1550,13 @@ test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (> [[package]] name = "jupyterlab" -version = "4.2.1" +version = "4.2.3" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.2.1-py3-none-any.whl", hash = "sha256:6ac6e3827b3c890e6e549800e8a4f4aaea6a69321e2240007902aa7a0c56a8e4"}, - {file = "jupyterlab-4.2.1.tar.gz", hash = "sha256:a10fb71085a6900820c62d43324005046402ffc8f0fde696103e37238a839507"}, + {file = "jupyterlab-4.2.3-py3-none-any.whl", hash = "sha256:0b59d11808e84bb84105c73364edfa867dd475492429ab34ea388a52f2e2e596"}, + {file = "jupyterlab-4.2.3.tar.gz", hash = "sha256:df6e46969ea51d66815167f23d92f105423b7f1f06fa604d4f44aeb018c82c7b"}, ] [package.dependencies] @@ -1569,6 +1570,7 @@ jupyter-server = ">=2.4.0,<3" jupyterlab-server = ">=2.27.1,<3" notebook-shim = ">=0.2" packaging = "*" +setuptools = ">=40.1.0" tomli = {version = ">=1.2.2", markers = "python_version < \"3.11\""} tornado = ">=6.2.0" traitlets = "*" @@ -2393,13 +2395,13 @@ test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "notebook" -version = "7.2.0" +version = "7.2.1" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.8" files = [ - {file = "notebook-7.2.0-py3-none-any.whl", hash = "sha256:b4752d7407d6c8872fc505df0f00d3cae46e8efb033b822adacbaa3f1f3ce8f5"}, - {file = "notebook-7.2.0.tar.gz", hash = "sha256:34a2ba4b08ad5d19ec930db7484fb79746a1784be9e1a5f8218f9af8656a141f"}, + {file = "notebook-7.2.1-py3-none-any.whl", hash = "sha256:f45489a3995746f2195a137e0773e2130960b51c9ac3ce257dbc2705aab3a6ca"}, + {file = "notebook-7.2.1.tar.gz", hash = "sha256:4287b6da59740b32173d01d641f763d292f49c30e7a51b89c46ba8473126341e"}, ] [package.dependencies] @@ -2433,47 +2435,56 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" [[package]] name = "numpy" -version = "1.26.4" +version = "2.0.0" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"}, + {file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"}, + {file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"}, + {file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"}, + {file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"}, + {file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"}, + {file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"}, + {file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"}, + {file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"}, + {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, ] [[package]] @@ -2498,57 +2509,62 @@ tests = ["pytest (>=4.6.2)"] [[package]] name = "orjson" -version = "3.10.3" +version = "3.10.6" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"}, - {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"}, - {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"}, - {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"}, - {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"}, - {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"}, - {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"}, - {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"}, - {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"}, - {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"}, - {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"}, - {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"}, - {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"}, - {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"}, - {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"}, - {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"}, + {file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"}, + {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"}, + {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"}, + {file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"}, + {file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"}, + {file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"}, + {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"}, + {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"}, + {file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"}, + {file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"}, + {file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"}, + {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"}, + {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"}, + {file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"}, + {file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"}, + {file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b"}, + {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e"}, + {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b"}, + {file = "orjson-3.10.6-cp38-none-win32.whl", hash = "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6"}, + {file = "orjson-3.10.6-cp38-none-win_amd64.whl", hash = "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7"}, + {file = "orjson-3.10.6-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb"}, + {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2"}, + {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a"}, + {file = "orjson-3.10.6-cp39-none-win32.whl", hash = "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219"}, + {file = "orjson-3.10.6-cp39-none-win_amd64.whl", hash = "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844"}, + {file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"}, ] [[package]] @@ -2581,13 +2597,13 @@ test = ["pytest"] [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -2740,84 +2756,95 @@ ptyprocess = ">=0.5" [[package]] name = "pillow" -version = "10.3.0" +version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, - {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, - {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, - {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, - {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, - {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, - {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, - {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, - {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, - {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, - {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, - {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, - {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, - {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, - {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, - {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, - {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -2871,13 +2898,13 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.45" +version = "3.0.47" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.45-py3-none-any.whl", hash = "sha256:a29b89160e494e3ea8622b09fa5897610b437884dcdcd054fdc1308883326c2a"}, - {file = "prompt_toolkit-3.0.45.tar.gz", hash = "sha256:07c60ee4ab7b7e90824b61afa840c8f5aad2d46b3e2e10acc33d8ecc94a49089"}, + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, ] [package.dependencies] @@ -2938,13 +2965,13 @@ tests = ["pytest"] [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.12.0" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, + {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, ] [[package]] @@ -3014,23 +3041,23 @@ files = [ [[package]] name = "pyinstaller" -version = "6.7.0" +version = "6.8.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.13,>=3.8" files = [ - {file = "pyinstaller-6.7.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:6decedba07031d1318528cb76d8400ae1572f7b08197f771ceca9e454e0060bf"}, - {file = "pyinstaller-6.7.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0756b3d4d3283ae2a5bda56abe479b80801ecafecdb3a96cd928542c2c75d016"}, - {file = "pyinstaller-6.7.0-py3-none-manylinux2014_i686.whl", hash = "sha256:df1b66500a7def997790bdadc23c142a2f96585ccd440beac63b72a4f3e41684"}, - {file = "pyinstaller-6.7.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:fa552214a8cbb5bfe4621c46a73c3cce12f299a520aa5ac397dc18718278f03a"}, - {file = "pyinstaller-6.7.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:5263ecbfd34a2297f0e5d41ecfcf7a6fb1ebbf60dbe0dc7c2d64f4a55871a99d"}, - {file = "pyinstaller-6.7.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4ff8ce04f1e5ab3a65d4a1ee6036cba648d0cdae6a7a33c6f0ca4ace46cdd43c"}, - {file = "pyinstaller-6.7.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:95efc2de7722213f376c5bac9620f390899f9a3c9eed70bd65adf29e2a085d5f"}, - {file = "pyinstaller-6.7.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:1b6dd6a50a7315214d345875cd08f8aa71025e7ba6bfa0f95c09285585e8d372"}, - {file = "pyinstaller-6.7.0-py3-none-win32.whl", hash = "sha256:73b94ce02b208c34eaabd032dd1522a3c03c0b3118a31bf7e4eafe7a9f4af2da"}, - {file = "pyinstaller-6.7.0-py3-none-win_amd64.whl", hash = "sha256:a3f85935b40f89e717f1e67377d3bfc953060e5795828ecf5357e2c1f7aa52bf"}, - {file = "pyinstaller-6.7.0-py3-none-win_arm64.whl", hash = "sha256:53038419ca09eea59de02dfb52453dd327983b0957821be610fb04cfd84676d0"}, - {file = "pyinstaller-6.7.0.tar.gz", hash = "sha256:8f09179c5f3d1b4b8453ac61adfe394dd416f9fc33abd7553f77d4897bc3a582"}, + {file = "pyinstaller-6.8.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ff6bc2784c1026f8e2f04aa3760cbed41408e108a9d4cf1dd52ee8351a3f6e1"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:39ac424d2ee2457d2ab11a5091436e75a0cccae207d460d180aa1fcbbafdd528"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_i686.whl", hash = "sha256:355832a3acc7de90a255ecacd4b9f9e166a547a79c8905d49f14e3a75c1acdb9"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:6303c7a009f47e6a96ef65aed49f41e36ece8d079b9193ca92fe807403e5fe80"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2b71509468c811968c0b5decb5bbe85b6292ea52d7b1f26313d2aabb673fa9a5"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ff31c5b99e05a4384bbe2071df67ec8b2b347640a375eae9b40218be2f1754c6"}, + {file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:000c36b13fe4cd8d0d8c2bc855b1ddcf39867b5adf389e6b5ca45b25fa3e619d"}, + {file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:fe0af018d7d5077180e3144ada89a4da5df8d07716eb7e9482834a56dc57a4e8"}, + {file = "pyinstaller-6.8.0-py3-none-win32.whl", hash = "sha256:d257f6645c7334cbd66f38a4fac62c3ad614cc46302b2b5d9f8cc48c563bce0e"}, + {file = "pyinstaller-6.8.0-py3-none-win_amd64.whl", hash = "sha256:81cccfa9b16699b457f4788c5cc119b50f3cd4d0db924955f15c33f2ad27a50d"}, + {file = "pyinstaller-6.8.0-py3-none-win_arm64.whl", hash = "sha256:1c3060a263758cf7f0144ab4c016097b20451b2469d468763414665db1bb743d"}, + {file = "pyinstaller-6.8.0.tar.gz", hash = "sha256:3f4b6520f4423fe19bcc2fd63ab7238851ae2bdcbc98f25bc5d2f97cc62012e9"}, ] [package.dependencies] @@ -3048,13 +3075,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2024.6" +version = "2024.7" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller_hooks_contrib-2024.6-py2.py3-none-any.whl", hash = "sha256:6cc88dad75261d9e1a7e0c6385139f35dcdbb16640c911a27f6078fe924a38cf"}, - {file = "pyinstaller_hooks_contrib-2024.6.tar.gz", hash = "sha256:3c188b3a79f5cd46d96520df3934642556a1b6ce8988ec5bbce820ada424bc2b"}, + {file = "pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl", hash = "sha256:8bf0775771fbaf96bcd2f4dfd6f7ae6c1dd1b1efe254c7e50477b3c08e7841d8"}, + {file = "pyinstaller_hooks_contrib-2024.7.tar.gz", hash = "sha256:fd5f37dcf99bece184e40642af88be16a9b89613ecb958a8bd1136634fc9fac5"}, ] [package.dependencies] @@ -3077,13 +3104,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "8.2.1" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, - {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -3548,32 +3575,32 @@ files = [ [[package]] name = "scikit-learn" -version = "1.5.0" +version = "1.5.1" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.9" files = [ - {file = "scikit_learn-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12e40ac48555e6b551f0a0a5743cc94cc5a765c9513fe708e01f0aa001da2801"}, - {file = "scikit_learn-1.5.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f405c4dae288f5f6553b10c4ac9ea7754d5180ec11e296464adb5d6ac68b6ef5"}, - {file = "scikit_learn-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df8ccabbf583315f13160a4bb06037bde99ea7d8211a69787a6b7c5d4ebb6fc3"}, - {file = "scikit_learn-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c75ea812cd83b1385bbfa94ae971f0d80adb338a9523f6bbcb5e0b0381151d4"}, - {file = "scikit_learn-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:a90c5da84829a0b9b4bf00daf62754b2be741e66b5946911f5bdfaa869fcedd6"}, - {file = "scikit_learn-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a65af2d8a6cce4e163a7951a4cfbfa7fceb2d5c013a4b593686c7f16445cf9d"}, - {file = "scikit_learn-1.5.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:4c0c56c3005f2ec1db3787aeaabefa96256580678cec783986836fc64f8ff622"}, - {file = "scikit_learn-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f77547165c00625551e5c250cefa3f03f2fc92c5e18668abd90bfc4be2e0bff"}, - {file = "scikit_learn-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:118a8d229a41158c9f90093e46b3737120a165181a1b58c03461447aa4657415"}, - {file = "scikit_learn-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:a03b09f9f7f09ffe8c5efffe2e9de1196c696d811be6798ad5eddf323c6f4d40"}, - {file = "scikit_learn-1.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:460806030c666addee1f074788b3978329a5bfdc9b7d63e7aad3f6d45c67a210"}, - {file = "scikit_learn-1.5.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1b94d6440603752b27842eda97f6395f570941857456c606eb1d638efdb38184"}, - {file = "scikit_learn-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d82c2e573f0f2f2f0be897e7a31fcf4e73869247738ab8c3ce7245549af58ab8"}, - {file = "scikit_learn-1.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3a10e1d9e834e84d05e468ec501a356226338778769317ee0b84043c0d8fb06"}, - {file = "scikit_learn-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:855fc5fa8ed9e4f08291203af3d3e5fbdc4737bd617a371559aaa2088166046e"}, - {file = "scikit_learn-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:40fb7d4a9a2db07e6e0cae4dc7bdbb8fada17043bac24104d8165e10e4cff1a2"}, - {file = "scikit_learn-1.5.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:47132440050b1c5beb95f8ba0b2402bbd9057ce96ec0ba86f2f445dd4f34df67"}, - {file = "scikit_learn-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174beb56e3e881c90424e21f576fa69c4ffcf5174632a79ab4461c4c960315ac"}, - {file = "scikit_learn-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261fe334ca48f09ed64b8fae13f9b46cc43ac5f580c4a605cbb0a517456c8f71"}, - {file = "scikit_learn-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:057b991ac64b3e75c9c04b5f9395eaf19a6179244c089afdebaad98264bff37c"}, - {file = "scikit_learn-1.5.0.tar.gz", hash = "sha256:789e3db01c750ed6d496fa2db7d50637857b451e57bcae863bff707c1247bef7"}, + {file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"}, + {file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"}, + {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"}, + {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"}, + {file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"}, + {file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"}, + {file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"}, + {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"}, + {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"}, + {file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"}, + {file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"}, + {file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"}, + {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"}, + {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"}, + {file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"}, + {file = "scikit_learn-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88e0672c7ac21eb149d409c74cc29f1d611d5158175846e7a9c2427bd12b3956"}, + {file = "scikit_learn-1.5.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:7b073a27797a283187a4ef4ee149959defc350b46cbf63a84d8514fe16b69855"}, + {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b59e3e62d2be870e5c74af4e793293753565c7383ae82943b83383fdcf5cc5c1"}, + {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd8d3a19d4bd6dc5a7d4f358c8c3a60934dc058f363c34c0ac1e9e12a31421d"}, + {file = "scikit_learn-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f57428de0c900a98389c4a433d4a3cf89de979b3aa24d1c1d251802aa15e44d"}, + {file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"}, ] [package.dependencies] @@ -3584,8 +3611,8 @@ threadpoolctl = ">=3.1.0" [package.extras] benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] -build = ["cython (>=3.0.10)", "meson-python (>=0.15.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.15.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] maintenance = ["conda-lock (==2.5.6)"] @@ -3593,45 +3620,45 @@ tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc ( [[package]] name = "scipy" -version = "1.13.1" +version = "1.14.0" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, - {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, - {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, - {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, - {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, - {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, - {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, - {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, - {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, - {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, - {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, - {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, - {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, - {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, - {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, - {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, - {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, - {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"}, + {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"}, + {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"}, + {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"}, + {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"}, + {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"}, + {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"}, + {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"}, + {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"}, + {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"}, + {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"}, + {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"}, + {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"}, + {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"}, ] [package.dependencies] -numpy = ">=1.22.4,<2.3" +numpy = ">=1.23.5,<2.3" [package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "send2trash" @@ -3651,18 +3678,18 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "70.0.0" +version = "70.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"}, + {file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -3743,15 +3770,15 @@ mpmath = ">=1.1.0,<1.4.0" [[package]] name = "tbb" -version = "2021.12.0" +version = "2021.13.0" description = "IntelĀ® oneAPI Threading Building Blocks (oneTBB)" optional = false python-versions = "*" files = [ - {file = "tbb-2021.12.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:f2cc9a7f8ababaa506cbff796ce97c3bf91062ba521e15054394f773375d81d8"}, - {file = "tbb-2021.12.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:a925e9a7c77d3a46ae31c34b0bb7f801c4118e857d137b68f68a8e458fcf2bd7"}, - {file = "tbb-2021.12.0-py3-none-win32.whl", hash = "sha256:b1725b30c174048edc8be70bd43bb95473f396ce895d91151a474d0fa9f450a8"}, - {file = "tbb-2021.12.0-py3-none-win_amd64.whl", hash = "sha256:fc2772d850229f2f3df85f1109c4844c495a2db7433d38200959ee9265b34789"}, + {file = "tbb-2021.13.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:a2567725329639519d46d92a2634cf61e76601dac2f777a05686fea546c4fe4f"}, + {file = "tbb-2021.13.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:aaf667e92849adb012b8874d6393282afc318aca4407fc62f912ee30a22da46a"}, + {file = "tbb-2021.13.0-py3-none-win32.whl", hash = "sha256:6669d26703e9943f6164c6407bd4a237a45007e79b8d3832fe6999576eaaa9ef"}, + {file = "tbb-2021.13.0-py3-none-win_amd64.whl", hash = "sha256:3528a53e4bbe64b07a6112b4c5a00ff3c61924ee46c9c68e004a1ac7ad1f09c3"}, ] [[package]] @@ -3817,21 +3844,21 @@ files = [ [[package]] name = "torch" -version = "2.3.0+cpu" +version = "2.3.1+cpu" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" optional = false python-versions = ">=3.8.0" files = [ - {file = "torch-2.3.0+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:e3c220702d82c7596924150e0499fbbffcf62a88a59adc860fa357cd8dc1c302"}, - {file = "torch-2.3.0+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:ab0c05525195b8fecdf2ea75968ed32ccd87dff16381b6e13249babb4a9596ff"}, - {file = "torch-2.3.0+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:97a38b25ee0e3d020691e7846efbca62a3d8a57645c027dcb5ba0adfec36fe55"}, - {file = "torch-2.3.0+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:a8ac195974be6f067245bae8156b8c06fb0a723b0eed8f2e244b5dd58c7e2a49"}, - {file = "torch-2.3.0+cpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:a8982e52185771591dad577a124a7770f72f288f8ae5833317b1e329c0d2f07e"}, - {file = "torch-2.3.0+cpu-cp312-cp312-win_amd64.whl", hash = "sha256:483131a7997995d867313ee902743084e844e830ab2a0c5e079c61ec2da3cd17"}, - {file = "torch-2.3.0+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:8c52484880d5fbe511cffc255dd34847ddeced3f94334c6bf7eb2b0445f10cb4"}, - {file = "torch-2.3.0+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:28a11bcc0d709b397d675cff689707019b8cc122e6bf328b57b900f47c36f156"}, - {file = "torch-2.3.0+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:1e86e225e472392440ace378ba3165b5e87648e8b5fbf16adc41c0df881c38b8"}, - {file = "torch-2.3.0+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:5c2afdff80203eaabf4c223a294c2f465020b3360e8e87f76b52ace9c5801ebe"}, + {file = "torch-2.3.1+cpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:d679e21d871982b9234444331a26350902cfd2d5ca44ce6f49896af8b3a3087d"}, + {file = "torch-2.3.1+cpu-cp310-cp310-win_amd64.whl", hash = "sha256:500bf790afc2fd374a15d06213242e517afccc50a46ea5955d321a9a68003335"}, + {file = "torch-2.3.1+cpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:a272defe305dbd944aa28a91cc3db0f0149495b3ebec2e39723a7224fa05dc57"}, + {file = "torch-2.3.1+cpu-cp311-cp311-win_amd64.whl", hash = "sha256:d2965eb54d3c8818e2280a54bd53e8246a6bb34e4b10bd19c59f35b611dd9f05"}, + {file = "torch-2.3.1+cpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:2141a6cb7021adf2f92a0fd372cfeac524ba460bd39ce3a641d30a561e41f69a"}, + {file = "torch-2.3.1+cpu-cp312-cp312-win_amd64.whl", hash = "sha256:6acdca2530462611095c44fd95af75ecd5b9646eac813452fe0adf31a9bc310a"}, + {file = "torch-2.3.1+cpu-cp38-cp38-linux_x86_64.whl", hash = "sha256:cab92d5101e6db686c5525e04d87cedbcf3a556073d71d07fbe7d1ce09630ffb"}, + {file = "torch-2.3.1+cpu-cp38-cp38-win_amd64.whl", hash = "sha256:dbc784569a367fd425158cf4ae82057dd3011185ba5fc68440432ba0562cb5b2"}, + {file = "torch-2.3.1+cpu-cp39-cp39-linux_x86_64.whl", hash = "sha256:a3cb8e61ba311cee1bb7463cbdcf3ebdfd071e2091e74c5785e3687eb02819f9"}, + {file = "torch-2.3.1+cpu-cp39-cp39-win_amd64.whl", hash = "sha256:df68668056e62c0332e03f43d9da5d4278b39df1ba58d30ec20d34242070955d"}, ] [package.dependencies] @@ -3915,22 +3942,22 @@ reference = "torch" [[package]] name = "tornado" -version = "6.4" +version = "6.4.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, ] [[package]] @@ -3981,13 +4008,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, - {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -4017,13 +4044,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -4089,18 +4116,18 @@ files = [ [[package]] name = "webcolors" -version = "1.13" +version = "24.6.0" description = "A library for working with the color formats defined by HTML and CSS." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "webcolors-1.13-py3-none-any.whl", hash = "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf"}, - {file = "webcolors-1.13.tar.gz", hash = "sha256:c225b674c83fa923be93d235330ce0300373d02885cef23238813b0d5668304a"}, + {file = "webcolors-24.6.0-py3-none-any.whl", hash = "sha256:8cf5bc7e28defd1d48b9e83d5fc30741328305a8195c29a8e668fa45586568a1"}, + {file = "webcolors-24.6.0.tar.gz", hash = "sha256:1d160d1de46b3e81e58d0a280d0c78b467dc80f47294b91b1ad8029d2cedb55b"}, ] [package.extras] docs = ["furo", "sphinx", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinxext-opengraph"] -tests = ["pytest", "pytest-cov"] +tests = ["coverage[toml]"] [[package]] name = "webencodings" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 00000000..084377a0 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +create = false diff --git a/project/Versions.scala b/project/Versions.scala index 2046b2a5..116cfb92 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -2,13 +2,13 @@ object Versions { val cpg = parseVersion("cpgVersion") val antlr = "4.13.1" - val scalatest = "3.2.18" + val scalatest = "3.2.19" val cats = "3.5.4" val json4s = "4.0.7" - val gradleTooling = "8.7" - val circe = "0.14.6" - val requests = "0.8.2" - val upickle = "3.3.0" + val gradleTooling = "8.8" + val circe = "0.14.9" + val requests = "0.8.3" + val upickle = "3.3.1" val scalaReplPP = "0.1.85" private def parseVersion(key: String): String = { diff --git a/project/meta-build.sbt b/project/meta-build.sbt index f4942268..2f97926d 100644 --- a/project/meta-build.sbt +++ b/project/meta-build.sbt @@ -4,5 +4,5 @@ libraryDependencies ++= Seq( "net.lingala.zip4j" % "zip4j" % "2.11.5", "com.github.pathikrit" %% "better-files" % "3.9.2", "net.java.dev.javacc" % "javacc" % "7.0.13", - "com.lihaoyi" %% "os-lib" % "0.10.0" + "com.lihaoyi" %% "os-lib" % "0.10.2" ) diff --git a/pyproject.toml b/pyproject.toml index 2fddc36c..77c028d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "appthreat-chen" -version = "2.1.0" +version = "2.1.1" description = "Code Hierarchy Exploration Net (chen)" authors = ["Team AppThreat "] license = "Apache-2.0" diff --git a/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/nodemethods/CfgNodeMethods.scala b/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/nodemethods/CfgNodeMethods.scala index 3c6f05a2..185cba76 100644 --- a/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/nodemethods/CfgNodeMethods.scala +++ b/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/nodemethods/CfgNodeMethods.scala @@ -73,26 +73,6 @@ class CfgNodeMethods(val node: CfgNode) extends AnyVal with NodeExtension: v._postDominateOut } - /** Using the post dominator tree, will determine if this node passes through the included set - * of nodes and filter it in. - * @param included - * the nodes this node must pass through. - * @return - * the traversal of this node if it passes through the included set. - */ - def passes(included: Set[CfgNode]): Iterator[CfgNode] = - Iterator.single(node).filter(_.postDominatedBy.exists(included.contains)) - - /** Using the post dominator tree, will determine if this node passes through the excluded set - * of nodes and filter it out. - * @param excluded - * the nodes this node must not pass through. - * @return - * the traversal of this node if it does not pass through the excluded set. - */ - def passesNot(excluded: Set[CfgNode]): Iterator[CfgNode] = - Iterator.single(node).filterNot(_.postDominatedBy.exists(excluded.contains)) - private def expandExhaustively(expand: CfgNode => Iterator[StoredNode]): Iterator[CfgNode] = var controllingNodes = List.empty[CfgNode] var visited = Set.empty + node @@ -115,6 +95,7 @@ class CfgNodeMethods(val node: CfgNode) extends AnyVal with NodeExtension: case _: MethodParameterIn | _: MethodParameterOut | _: MethodReturn => walkUpAst(node) case _: CallRepr if !node.isInstanceOf[Call] => walkUpAst(node) + case _: Annotation | _: AnnotationLiteral => node.inAst.collectAll[Method].head case _: Expression | _: JumpTarget => walkUpContains(node) /** Obtain hexadecimal string representation of lineNumber field. diff --git a/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/types/expressions/generalizations/CfgNodeTraversal.scala b/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/types/expressions/generalizations/CfgNodeTraversal.scala index 230ffea3..bc64bd8a 100644 --- a/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/types/expressions/generalizations/CfgNodeTraversal.scala +++ b/semanticcpg/src/main/scala/io/shiftleft/semanticcpg/language/types/expressions/generalizations/CfgNodeTraversal.scala @@ -89,13 +89,4 @@ class CfgNodeTraversal[A <: CfgNode](val traversal: Iterator[A]) extends AnyVal: def address: Iterator[Option[String]] = traversal.map(_.address) - @Doc(info = "Filters in paths that pass though the given traversal") - def passes(included: Iterator[CfgNode]): Iterator[CfgNode] = - val in = included.toSet - traversal.flatMap(_.passes(in)) - - @Doc(info = "Filters out paths that pass though the given traversal") - def passesNot(excluded: Iterator[CfgNode]): Iterator[CfgNode] = - val ex = excluded.toSet - traversal.flatMap(_.passesNot(ex)) end CfgNodeTraversal