mirror of
https://github.com/github/codeql.git
synced 2026-04-24 08:15:14 +02:00
Merge branch 'github:main' into crypto-test
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
ql/python/ql/src/AlertSuppression.ql
|
||||
ql/python/ql/src/Classes/MaybeUndefinedClassAttribute.ql
|
||||
ql/python/ql/src/Classes/ShouldBeContextManager.ql
|
||||
ql/python/ql/src/Classes/UndefinedClassAttribute.ql
|
||||
ql/python/ql/src/Classes/UselessClass.ql
|
||||
ql/python/ql/src/Expressions/NonPortableComparisonUsingIs.ql
|
||||
ql/python/ql/src/Filters/ClassifyFiles.ql
|
||||
ql/python/ql/src/Functions/ReturnValueIgnored.ql
|
||||
ql/python/ql/src/Imports/ImportShadowedByLoopVar.ql
|
||||
ql/python/ql/src/Imports/ImportStarUsed.ql
|
||||
ql/python/ql/src/Imports/Imports.ql
|
||||
ql/python/ql/src/Lexical/FCommentedOutCode.ql
|
||||
ql/python/ql/src/Lexical/ToDoComment.ql
|
||||
ql/python/ql/src/Metrics/CLinesOfCode.ql
|
||||
ql/python/ql/src/Metrics/ClassAfferentCoupling.ql
|
||||
ql/python/ql/src/Metrics/ClassEfferentCoupling.ql
|
||||
ql/python/ql/src/Metrics/CommentRatio.ql
|
||||
ql/python/ql/src/Metrics/CyclomaticComplexity.ql
|
||||
ql/python/ql/src/Metrics/Dependencies/ExternalDependencies.ql
|
||||
ql/python/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql
|
||||
ql/python/ql/src/Metrics/DirectImports.ql
|
||||
ql/python/ql/src/Metrics/DocStringRatio.ql
|
||||
ql/python/ql/src/Metrics/External/CommitDisplayStrings.ql
|
||||
ql/python/ql/src/Metrics/External/CommitSourceLinks.ql
|
||||
ql/python/ql/src/Metrics/FClasses.ql
|
||||
ql/python/ql/src/Metrics/FFunctionsAndMethods.ql
|
||||
ql/python/ql/src/Metrics/FLines.ql
|
||||
ql/python/ql/src/Metrics/FLinesOfCode.ql
|
||||
ql/python/ql/src/Metrics/FLinesOfComments.ql
|
||||
ql/python/ql/src/Metrics/FLinesOfDuplicatedCode.ql
|
||||
ql/python/ql/src/Metrics/FLinesOfSimilarCode.ql
|
||||
ql/python/ql/src/Metrics/FNumberOfTests.ql
|
||||
ql/python/ql/src/Metrics/FunctionNumberOfCalls.ql
|
||||
ql/python/ql/src/Metrics/FunctionStatementNestingDepth.ql
|
||||
ql/python/ql/src/Metrics/History/HChurn.ql
|
||||
ql/python/ql/src/Metrics/History/HLinesAdded.ql
|
||||
ql/python/ql/src/Metrics/History/HLinesDeleted.ql
|
||||
ql/python/ql/src/Metrics/History/HNumberOfAuthors.ql
|
||||
ql/python/ql/src/Metrics/History/HNumberOfCoCommits.ql
|
||||
ql/python/ql/src/Metrics/History/HNumberOfCommits.ql
|
||||
ql/python/ql/src/Metrics/History/HNumberOfReCommits.ql
|
||||
ql/python/ql/src/Metrics/History/HNumberOfRecentAuthors.ql
|
||||
ql/python/ql/src/Metrics/History/HNumberOfRecentChangedFiles.ql
|
||||
ql/python/ql/src/Metrics/History/HNumberOfRecentCommits.ql
|
||||
ql/python/ql/src/Metrics/Internal/CallableDisplayStrings.ql
|
||||
ql/python/ql/src/Metrics/Internal/CallableExtents.ql
|
||||
ql/python/ql/src/Metrics/Internal/CallableSourceLinks.ql
|
||||
ql/python/ql/src/Metrics/Internal/ClassDisplayStrings.ql
|
||||
ql/python/ql/src/Metrics/Internal/ClassExtents.ql
|
||||
ql/python/ql/src/Metrics/Internal/ClassSourceLinks.ql
|
||||
ql/python/ql/src/Metrics/Internal/TypeAnnotations.ql
|
||||
ql/python/ql/src/Metrics/LackofCohesionInMethodsCK.ql
|
||||
ql/python/ql/src/Metrics/LackofCohesionInMethodsHM.ql
|
||||
ql/python/ql/src/Metrics/ModuleAfferentCoupling.ql
|
||||
ql/python/ql/src/Metrics/ModuleEfferentCoupling.ql
|
||||
ql/python/ql/src/Metrics/NumberOfParametersWithoutDefault.ql
|
||||
ql/python/ql/src/Metrics/NumberOfStatements.ql
|
||||
ql/python/ql/src/Metrics/TransitiveImports.ql
|
||||
ql/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql
|
||||
ql/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql
|
||||
ql/python/ql/src/Statements/AssertLiteralConstant.ql
|
||||
ql/python/ql/src/Statements/C_StyleParentheses.ql
|
||||
ql/python/ql/src/Statements/DocStrings.ql
|
||||
ql/python/ql/src/Statements/ExecUsed.ql
|
||||
ql/python/ql/src/Statements/StringConcatenationInLoop.ql
|
||||
ql/python/ql/src/Variables/Global.ql
|
||||
ql/python/ql/src/Variables/ShadowBuiltin.ql
|
||||
ql/python/ql/src/Variables/ShadowGlobal.ql
|
||||
ql/python/ql/src/Variables/UndefinedGlobal.ql
|
||||
ql/python/ql/src/Variables/UnusedParameter.ql
|
||||
ql/python/ql/src/analysis/CallGraphEfficiency.ql
|
||||
ql/python/ql/src/analysis/CallGraphMarginalEfficiency.ql
|
||||
ql/python/ql/src/analysis/Consistency.ql
|
||||
ql/python/ql/src/analysis/ContextEfficiency.ql
|
||||
ql/python/ql/src/analysis/ContextMarginalEfficiency.ql
|
||||
ql/python/ql/src/analysis/Definitions.ql
|
||||
ql/python/ql/src/analysis/Efficiency.ql
|
||||
ql/python/ql/src/analysis/FailedInference.ql
|
||||
ql/python/ql/src/analysis/ImportFailure.ql
|
||||
ql/python/ql/src/analysis/KeyPointsToFailure.ql
|
||||
ql/python/ql/src/analysis/PointsToFailure.ql
|
||||
ql/python/ql/src/analysis/Pruned.ql
|
||||
ql/python/ql/src/analysis/RatioOfDefinitions.ql
|
||||
ql/python/ql/src/analysis/Summary.ql
|
||||
ql/python/ql/src/analysis/TypeHierarchyFailure.ql
|
||||
ql/python/ql/src/analysis/TypeInferenceFailure.ql
|
||||
ql/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
|
||||
ql/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-022/ZipSlip.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-074/remoteCommandExecution/RemoteCommandExecution.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-079/EmailXss.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-091/XsltInjection.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-094/Js2Py.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-1236/CsvInjection.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-176/UnicodeBypassValidation.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/WebAppConstantSecretKey.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-287/ImproperLdapAuth.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-338/InsecureRandomness.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-347/JWTEmptyKeyOrAlgorithm.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-409/DecompressionBombs.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-522/LdapInsecureAuth.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-611/SimpleXmlRpcServer.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql
|
||||
ql/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/UnknownAsymmetricKeyGen.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakAsymmetricKeyGen.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakAsymmetricPadding.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakBlockMode.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakBlockModeIVorNonce.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakEllipticCurve.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakHashes.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFAlgorithm.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFIteration.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFKeyLength.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFMode.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFSaltGen.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFSaltSize.ql
|
||||
ql/python/ql/src/experimental/cryptography/example_alerts/WeakSymmetricEncryption.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/AllAsymmetricAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/AllCryptoAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/AsymmetricEncryptionAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/AsymmetricKeyGenOperation.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/AsymmetricPaddingAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/AuthenticatedEncryptionAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/BlockModeAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/BlockModeKnownIVsOrNonces.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/BlockModeUnknownIVsOrNonces.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/EllipticCurveAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/HashingAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/KeyDerivationAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/KeyExchangeAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/SigningAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/SymmetricEncryptionAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/new_models/SymmetricPaddingAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/old_models/AllCryptoAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/old_models/BlockModeAlgorithms.ql
|
||||
ql/python/ql/src/experimental/cryptography/inventory/old_models/HashingAlgorithms.ql
|
||||
ql/python/ql/src/external/DuplicateBlock.ql
|
||||
ql/python/ql/src/external/DuplicateFunction.ql
|
||||
ql/python/ql/src/external/MostlyDuplicateClass.ql
|
||||
ql/python/ql/src/external/MostlyDuplicateFile.ql
|
||||
ql/python/ql/src/external/MostlySimilarFile.ql
|
||||
ql/python/ql/src/external/SimilarFunction.ql
|
||||
ql/python/ql/src/meta/ClassHierarchy/Find.ql
|
||||
ql/python/ql/src/meta/alerts/InterestingTaintSinks.ql
|
||||
ql/python/ql/src/meta/alerts/RemoteFlowSources.ql
|
||||
ql/python/ql/src/meta/alerts/RemoteFlowSourcesReach.ql
|
||||
ql/python/ql/src/meta/alerts/RequestHandlers.ql
|
||||
ql/python/ql/src/meta/alerts/TaintSinks.ql
|
||||
ql/python/ql/src/meta/analysis-quality/CallGraph.ql
|
||||
ql/python/ql/src/meta/analysis-quality/PointsToResolvableCallRatio.ql
|
||||
ql/python/ql/src/meta/analysis-quality/PointsToResolvableCalls.ql
|
||||
ql/python/ql/src/meta/analysis-quality/PointsToResolvableCallsRelevantTarget.ql
|
||||
ql/python/ql/src/meta/analysis-quality/ResolvableCallCandidates.ql
|
||||
ql/python/ql/src/meta/analysis-quality/SummarizedCallableCallSites.ql
|
||||
ql/python/ql/src/meta/analysis-quality/TTCallGraph.ql
|
||||
ql/python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql
|
||||
ql/python/ql/src/meta/analysis-quality/TTCallGraphNew.ql
|
||||
ql/python/ql/src/meta/analysis-quality/TTCallGraphNewAmbiguous.ql
|
||||
ql/python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql
|
||||
ql/python/ql/src/meta/analysis-quality/TTCallGraphShared.ql
|
||||
ql/python/ql/src/meta/debug/DebugStats.ql
|
||||
ql/python/ql/src/meta/debug/SimpleClassDebug.ql
|
||||
ql/python/ql/src/utils/modeleditor/FrameworkModeEndpoints.ql
|
||||
@@ -0,0 +1,4 @@
|
||||
ql/python/ql/src/Functions/NonCls.ql
|
||||
ql/python/ql/src/Functions/NonSelf.ql
|
||||
ql/python/ql/src/Functions/SignatureSpecialMethods.ql
|
||||
ql/python/ql/src/Resources/FileNotAlwaysClosed.ql
|
||||
@@ -0,0 +1,43 @@
|
||||
ql/python/ql/src/Diagnostics/ExtractedFiles.ql
|
||||
ql/python/ql/src/Diagnostics/ExtractionWarnings.ql
|
||||
ql/python/ql/src/Expressions/UseofInput.ql
|
||||
ql/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql
|
||||
ql/python/ql/src/Security/CWE-020/CookieInjection.ql
|
||||
ql/python/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql
|
||||
ql/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql
|
||||
ql/python/ql/src/Security/CWE-020/OverlyLargeRange.ql
|
||||
ql/python/ql/src/Security/CWE-022/PathInjection.ql
|
||||
ql/python/ql/src/Security/CWE-074/TemplateInjection.ql
|
||||
ql/python/ql/src/Security/CWE-078/CommandInjection.ql
|
||||
ql/python/ql/src/Security/CWE-079/ReflectedXss.ql
|
||||
ql/python/ql/src/Security/CWE-089/SqlInjection.ql
|
||||
ql/python/ql/src/Security/CWE-090/LdapInjection.ql
|
||||
ql/python/ql/src/Security/CWE-094/CodeInjection.ql
|
||||
ql/python/ql/src/Security/CWE-113/HeaderInjection.ql
|
||||
ql/python/ql/src/Security/CWE-116/BadTagFilter.ql
|
||||
ql/python/ql/src/Security/CWE-209/StackTraceExposure.ql
|
||||
ql/python/ql/src/Security/CWE-215/FlaskDebug.ql
|
||||
ql/python/ql/src/Security/CWE-285/PamAuthorization.ql
|
||||
ql/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql
|
||||
ql/python/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/python/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
ql/python/ql/src/Security/CWE-326/WeakCryptoKey.ql
|
||||
ql/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
ql/python/ql/src/Security/CWE-327/InsecureDefaultProtocol.ql
|
||||
ql/python/ql/src/Security/CWE-327/InsecureProtocol.ql
|
||||
ql/python/ql/src/Security/CWE-327/WeakSensitiveDataHashing.ql
|
||||
ql/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql
|
||||
ql/python/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
|
||||
ql/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql
|
||||
ql/python/ql/src/Security/CWE-601/UrlRedirect.ql
|
||||
ql/python/ql/src/Security/CWE-611/Xxe.ql
|
||||
ql/python/ql/src/Security/CWE-614/InsecureCookie.ql
|
||||
ql/python/ql/src/Security/CWE-643/XpathInjection.ql
|
||||
ql/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
|
||||
ql/python/ql/src/Security/CWE-730/ReDoS.ql
|
||||
ql/python/ql/src/Security/CWE-730/RegexInjection.ql
|
||||
ql/python/ql/src/Security/CWE-776/XmlBomb.ql
|
||||
ql/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql
|
||||
ql/python/ql/src/Security/CWE-943/NoSqlInjection.ql
|
||||
ql/python/ql/src/Summary/LinesOfCode.ql
|
||||
ql/python/ql/src/Summary/LinesOfUserCode.ql
|
||||
@@ -0,0 +1,173 @@
|
||||
ql/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql
|
||||
ql/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql
|
||||
ql/python/ql/src/Classes/EqualsOrHash.ql
|
||||
ql/python/ql/src/Classes/EqualsOrNotEquals.ql
|
||||
ql/python/ql/src/Classes/IncompleteOrdering.ql
|
||||
ql/python/ql/src/Classes/InconsistentMRO.ql
|
||||
ql/python/ql/src/Classes/InitCallsSubclassMethod.ql
|
||||
ql/python/ql/src/Classes/MissingCallToDel.ql
|
||||
ql/python/ql/src/Classes/MissingCallToInit.ql
|
||||
ql/python/ql/src/Classes/MutatingDescriptor.ql
|
||||
ql/python/ql/src/Classes/OverwritingAttributeInSuperClass.ql
|
||||
ql/python/ql/src/Classes/PropertyInOldStyleClass.ql
|
||||
ql/python/ql/src/Classes/SlotsInOldStyleClass.ql
|
||||
ql/python/ql/src/Classes/SubclassShadowing.ql
|
||||
ql/python/ql/src/Classes/SuperInOldStyleClass.ql
|
||||
ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql
|
||||
ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql
|
||||
ql/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql
|
||||
ql/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql
|
||||
ql/python/ql/src/Diagnostics/ExtractedFiles.ql
|
||||
ql/python/ql/src/Diagnostics/ExtractionWarnings.ql
|
||||
ql/python/ql/src/Exceptions/CatchingBaseException.ql
|
||||
ql/python/ql/src/Exceptions/EmptyExcept.ql
|
||||
ql/python/ql/src/Exceptions/IllegalExceptionHandlerType.ql
|
||||
ql/python/ql/src/Exceptions/IllegalRaise.ql
|
||||
ql/python/ql/src/Exceptions/IncorrectExceptOrder.ql
|
||||
ql/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql
|
||||
ql/python/ql/src/Exceptions/RaisingTuple.ql
|
||||
ql/python/ql/src/Exceptions/UnguardedNextInGenerator.ql
|
||||
ql/python/ql/src/Expressions/CallToSuperWrongClass.ql
|
||||
ql/python/ql/src/Expressions/CompareConstants.ql
|
||||
ql/python/ql/src/Expressions/CompareIdenticalValues.ql
|
||||
ql/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.ql
|
||||
ql/python/ql/src/Expressions/Comparisons/UselessComparisonTest.ql
|
||||
ql/python/ql/src/Expressions/ContainsNonContainer.ql
|
||||
ql/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql
|
||||
ql/python/ql/src/Expressions/EqualsNone.ql
|
||||
ql/python/ql/src/Expressions/ExpectedMappingForFormatString.ql
|
||||
ql/python/ql/src/Expressions/ExplicitCallToDel.ql
|
||||
ql/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql
|
||||
ql/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql
|
||||
ql/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql
|
||||
ql/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql
|
||||
ql/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql
|
||||
ql/python/ql/src/Expressions/HashedButNoHash.ql
|
||||
ql/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql
|
||||
ql/python/ql/src/Expressions/NonCallableCalled.ql
|
||||
ql/python/ql/src/Expressions/Regex/BackspaceEscape.ql
|
||||
ql/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.ql
|
||||
ql/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.ql
|
||||
ql/python/ql/src/Expressions/Regex/UnmatchableCaret.ql
|
||||
ql/python/ql/src/Expressions/Regex/UnmatchableDollar.ql
|
||||
ql/python/ql/src/Expressions/TruncatedDivision.ql
|
||||
ql/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.ql
|
||||
ql/python/ql/src/Expressions/UnnecessaryLambda.ql
|
||||
ql/python/ql/src/Expressions/UnsupportedFormatCharacter.ql
|
||||
ql/python/ql/src/Expressions/UseofApply.ql
|
||||
ql/python/ql/src/Expressions/UseofInput.ql
|
||||
ql/python/ql/src/Expressions/WrongNameForArgumentInCall.ql
|
||||
ql/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql
|
||||
ql/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql
|
||||
ql/python/ql/src/Functions/ConsistentReturns.ql
|
||||
ql/python/ql/src/Functions/DeprecatedSliceMethod.ql
|
||||
ql/python/ql/src/Functions/ExplicitReturnInInit.ql
|
||||
ql/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
|
||||
ql/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql
|
||||
ql/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql
|
||||
ql/python/ql/src/Functions/InitIsGenerator.ql
|
||||
ql/python/ql/src/Functions/IterReturnsNonIterator.ql
|
||||
ql/python/ql/src/Functions/IterReturnsNonSelf.ql
|
||||
ql/python/ql/src/Functions/ModificationOfParameterWithDefault.ql
|
||||
ql/python/ql/src/Functions/NonCls.ql
|
||||
ql/python/ql/src/Functions/NonSelf.ql
|
||||
ql/python/ql/src/Functions/OverlyComplexDelMethod.ql
|
||||
ql/python/ql/src/Functions/ReturnConsistentTupleSizes.ql
|
||||
ql/python/ql/src/Functions/SignatureOverriddenMethod.ql
|
||||
ql/python/ql/src/Functions/SignatureSpecialMethods.ql
|
||||
ql/python/ql/src/Functions/UseImplicitNoneReturnValue.ql
|
||||
ql/python/ql/src/Imports/CyclicImport.ql
|
||||
ql/python/ql/src/Imports/DeprecatedModule.ql
|
||||
ql/python/ql/src/Imports/EncodingError.ql
|
||||
ql/python/ql/src/Imports/FromImportOfMutableAttribute.ql
|
||||
ql/python/ql/src/Imports/ImportandImportFrom.ql
|
||||
ql/python/ql/src/Imports/ModuleImportsItself.ql
|
||||
ql/python/ql/src/Imports/ModuleLevelCyclicImport.ql
|
||||
ql/python/ql/src/Imports/MultipleImports.ql
|
||||
ql/python/ql/src/Imports/SyntaxError.ql
|
||||
ql/python/ql/src/Imports/UnintentionalImport.ql
|
||||
ql/python/ql/src/Imports/UnusedImport.ql
|
||||
ql/python/ql/src/Lexical/CommentedOutCode.ql
|
||||
ql/python/ql/src/Lexical/OldOctalLiteral.ql
|
||||
ql/python/ql/src/Numerics/Pythagorean.ql
|
||||
ql/python/ql/src/Resources/FileNotAlwaysClosed.ql
|
||||
ql/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql
|
||||
ql/python/ql/src/Security/CWE-020/CookieInjection.ql
|
||||
ql/python/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql
|
||||
ql/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql
|
||||
ql/python/ql/src/Security/CWE-020/OverlyLargeRange.ql
|
||||
ql/python/ql/src/Security/CWE-022/PathInjection.ql
|
||||
ql/python/ql/src/Security/CWE-022/TarSlip.ql
|
||||
ql/python/ql/src/Security/CWE-074/TemplateInjection.ql
|
||||
ql/python/ql/src/Security/CWE-078/CommandInjection.ql
|
||||
ql/python/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql
|
||||
ql/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql
|
||||
ql/python/ql/src/Security/CWE-079/ReflectedXss.ql
|
||||
ql/python/ql/src/Security/CWE-089/SqlInjection.ql
|
||||
ql/python/ql/src/Security/CWE-090/LdapInjection.ql
|
||||
ql/python/ql/src/Security/CWE-094/CodeInjection.ql
|
||||
ql/python/ql/src/Security/CWE-113/HeaderInjection.ql
|
||||
ql/python/ql/src/Security/CWE-116/BadTagFilter.ql
|
||||
ql/python/ql/src/Security/CWE-117/LogInjection.ql
|
||||
ql/python/ql/src/Security/CWE-209/StackTraceExposure.ql
|
||||
ql/python/ql/src/Security/CWE-215/FlaskDebug.ql
|
||||
ql/python/ql/src/Security/CWE-285/PamAuthorization.ql
|
||||
ql/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql
|
||||
ql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql
|
||||
ql/python/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/python/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
ql/python/ql/src/Security/CWE-326/WeakCryptoKey.ql
|
||||
ql/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
ql/python/ql/src/Security/CWE-327/InsecureDefaultProtocol.ql
|
||||
ql/python/ql/src/Security/CWE-327/InsecureProtocol.ql
|
||||
ql/python/ql/src/Security/CWE-327/WeakSensitiveDataHashing.ql
|
||||
ql/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql
|
||||
ql/python/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
|
||||
ql/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql
|
||||
ql/python/ql/src/Security/CWE-601/UrlRedirect.ql
|
||||
ql/python/ql/src/Security/CWE-611/Xxe.ql
|
||||
ql/python/ql/src/Security/CWE-614/InsecureCookie.ql
|
||||
ql/python/ql/src/Security/CWE-643/XpathInjection.ql
|
||||
ql/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
|
||||
ql/python/ql/src/Security/CWE-730/ReDoS.ql
|
||||
ql/python/ql/src/Security/CWE-730/RegexInjection.ql
|
||||
ql/python/ql/src/Security/CWE-732/WeakFilePermissions.ql
|
||||
ql/python/ql/src/Security/CWE-776/XmlBomb.ql
|
||||
ql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
|
||||
ql/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql
|
||||
ql/python/ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql
|
||||
ql/python/ql/src/Security/CWE-943/NoSqlInjection.ql
|
||||
ql/python/ql/src/Statements/AssertOnTuple.ql
|
||||
ql/python/ql/src/Statements/BreakOrReturnInFinally.ql
|
||||
ql/python/ql/src/Statements/ConstantInConditional.ql
|
||||
ql/python/ql/src/Statements/IterableStringOrSequence.ql
|
||||
ql/python/ql/src/Statements/MismatchInMultipleAssignment.ql
|
||||
ql/python/ql/src/Statements/ModificationOfLocals.ql
|
||||
ql/python/ql/src/Statements/NestedLoopsSameVariable.ql
|
||||
ql/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql
|
||||
ql/python/ql/src/Statements/NonIteratorInForLoop.ql
|
||||
ql/python/ql/src/Statements/RedundantAssignment.ql
|
||||
ql/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql
|
||||
ql/python/ql/src/Statements/ShouldUseWithStatement.ql
|
||||
ql/python/ql/src/Statements/SideEffectInAssert.ql
|
||||
ql/python/ql/src/Statements/StatementNoEffect.ql
|
||||
ql/python/ql/src/Statements/TopLevelPrint.ql
|
||||
ql/python/ql/src/Statements/UnnecessaryDelete.ql
|
||||
ql/python/ql/src/Statements/UnnecessaryElseClause.ql
|
||||
ql/python/ql/src/Statements/UnnecessaryPass.ql
|
||||
ql/python/ql/src/Statements/UnreachableCode.ql
|
||||
ql/python/ql/src/Statements/UnusedExceptionObject.ql
|
||||
ql/python/ql/src/Statements/UseOfExit.ql
|
||||
ql/python/ql/src/Summary/LinesOfCode.ql
|
||||
ql/python/ql/src/Summary/LinesOfUserCode.ql
|
||||
ql/python/ql/src/Testing/ImpreciseAssert.ql
|
||||
ql/python/ql/src/Variables/GlobalAtModuleLevel.ql
|
||||
ql/python/ql/src/Variables/LeakingListComprehension.ql
|
||||
ql/python/ql/src/Variables/LoopVariableCapture/LoopVariableCapture.ql
|
||||
ql/python/ql/src/Variables/MultiplyDefined.ql
|
||||
ql/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql
|
||||
ql/python/ql/src/Variables/UndefinedExport.ql
|
||||
ql/python/ql/src/Variables/UndefinedPlaceHolder.ql
|
||||
ql/python/ql/src/Variables/UninitializedLocal.ql
|
||||
ql/python/ql/src/Variables/UnusedLocalVariable.ql
|
||||
ql/python/ql/src/Variables/UnusedModuleVariable.ql
|
||||
@@ -0,0 +1,51 @@
|
||||
ql/python/ql/src/Diagnostics/ExtractedFiles.ql
|
||||
ql/python/ql/src/Diagnostics/ExtractionWarnings.ql
|
||||
ql/python/ql/src/Expressions/UseofInput.ql
|
||||
ql/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql
|
||||
ql/python/ql/src/Security/CWE-020/CookieInjection.ql
|
||||
ql/python/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql
|
||||
ql/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql
|
||||
ql/python/ql/src/Security/CWE-020/OverlyLargeRange.ql
|
||||
ql/python/ql/src/Security/CWE-022/PathInjection.ql
|
||||
ql/python/ql/src/Security/CWE-022/TarSlip.ql
|
||||
ql/python/ql/src/Security/CWE-074/TemplateInjection.ql
|
||||
ql/python/ql/src/Security/CWE-078/CommandInjection.ql
|
||||
ql/python/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql
|
||||
ql/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql
|
||||
ql/python/ql/src/Security/CWE-079/ReflectedXss.ql
|
||||
ql/python/ql/src/Security/CWE-089/SqlInjection.ql
|
||||
ql/python/ql/src/Security/CWE-090/LdapInjection.ql
|
||||
ql/python/ql/src/Security/CWE-094/CodeInjection.ql
|
||||
ql/python/ql/src/Security/CWE-113/HeaderInjection.ql
|
||||
ql/python/ql/src/Security/CWE-116/BadTagFilter.ql
|
||||
ql/python/ql/src/Security/CWE-117/LogInjection.ql
|
||||
ql/python/ql/src/Security/CWE-209/StackTraceExposure.ql
|
||||
ql/python/ql/src/Security/CWE-215/FlaskDebug.ql
|
||||
ql/python/ql/src/Security/CWE-285/PamAuthorization.ql
|
||||
ql/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql
|
||||
ql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql
|
||||
ql/python/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/python/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
ql/python/ql/src/Security/CWE-326/WeakCryptoKey.ql
|
||||
ql/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
ql/python/ql/src/Security/CWE-327/InsecureDefaultProtocol.ql
|
||||
ql/python/ql/src/Security/CWE-327/InsecureProtocol.ql
|
||||
ql/python/ql/src/Security/CWE-327/WeakSensitiveDataHashing.ql
|
||||
ql/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql
|
||||
ql/python/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
|
||||
ql/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql
|
||||
ql/python/ql/src/Security/CWE-601/UrlRedirect.ql
|
||||
ql/python/ql/src/Security/CWE-611/Xxe.ql
|
||||
ql/python/ql/src/Security/CWE-614/InsecureCookie.ql
|
||||
ql/python/ql/src/Security/CWE-643/XpathInjection.ql
|
||||
ql/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
|
||||
ql/python/ql/src/Security/CWE-730/ReDoS.ql
|
||||
ql/python/ql/src/Security/CWE-730/RegexInjection.ql
|
||||
ql/python/ql/src/Security/CWE-732/WeakFilePermissions.ql
|
||||
ql/python/ql/src/Security/CWE-776/XmlBomb.ql
|
||||
ql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
|
||||
ql/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql
|
||||
ql/python/ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql
|
||||
ql/python/ql/src/Security/CWE-943/NoSqlInjection.ql
|
||||
ql/python/ql/src/Summary/LinesOfCode.ql
|
||||
ql/python/ql/src/Summary/LinesOfUserCode.ql
|
||||
14
python/ql/integration-tests/query-suite/test.py
Normal file
14
python/ql/integration-tests/query-suite/test.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import runs_on
|
||||
import pytest
|
||||
from query_suites import *
|
||||
|
||||
well_known_query_suites = ['python-code-quality.qls', 'python-security-and-quality.qls', 'python-security-extended.qls', 'python-code-scanning.qls']
|
||||
|
||||
@runs_on.posix
|
||||
@pytest.mark.parametrize("query_suite", well_known_query_suites)
|
||||
def test(codeql, python, check_query_suite, query_suite):
|
||||
check_query_suite(query_suite)
|
||||
|
||||
@runs_on.posix
|
||||
def test_not_included_queries(codeql, python, check_queries_not_included):
|
||||
check_queries_not_included('python', well_known_query_suites)
|
||||
@@ -1,3 +1,55 @@
|
||||
## 4.0.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 4.0.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 4.0.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- Added the methods `getMinArguments` and `getMaxArguments` to the `Function` class. These return the minimum and maximum positional arguments that the given function accepts.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- `MatchLiteralPattern`s such as `case None: ...` are now never pruned from the extracted source code. This fixes some situations where code was wrongly identified as unreachable.
|
||||
|
||||
## 4.0.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 4.0.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 4.0.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed a bug in the extractor where a comment inside a subscript could sometimes cause the AST to be missing nodes.
|
||||
- Using the `break` and `continue` keywords outside of a loop, which is a syntax error but is accepted by our parser, would cause the control-flow construction to fail. This is now no longer the case.
|
||||
|
||||
## 4.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Deleted the old deprecated TypeTracking library.
|
||||
* Deleted the deprecated `classRef` predicate from the `FieldStorage` module, use `subclassRef` instead.
|
||||
* Deleted a lot of deprecated modules and predicates from `Stdlib.qll`, use API-graphs directly instead.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Additional data flow models for the builtin functions `map`, `filter`, `zip`, and `enumerate` have been added.
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The sensitive data library has been improved so that `snake_case` style variable names are recognized more reliably. This may result in more sensitive data being identified, and more results from queries that use the sensitive data library.
|
||||
- Additional taint steps through methods of `lxml.etree.Element` and `lxml.etree.ElementTree` objects from the `lxml` PyPI package have been modeled.
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
- Additional taint steps through methods of `lxml.etree.Element` and `lxml.etree.ElementTree` objects from the `lxml` PyPI package have been modeled.
|
||||
6
python/ql/lib/change-notes/released/3.1.1.md
Normal file
6
python/ql/lib/change-notes/released/3.1.1.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 3.1.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The sensitive data library has been improved so that `snake_case` style variable names are recognized more reliably. This may result in more sensitive data being identified, and more results from queries that use the sensitive data library.
|
||||
- Additional taint steps through methods of `lxml.etree.Element` and `lxml.etree.ElementTree` objects from the `lxml` PyPI package have been modeled.
|
||||
11
python/ql/lib/change-notes/released/4.0.0.md
Normal file
11
python/ql/lib/change-notes/released/4.0.0.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## 4.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Deleted the old deprecated TypeTracking library.
|
||||
* Deleted the deprecated `classRef` predicate from the `FieldStorage` module, use `subclassRef` instead.
|
||||
* Deleted a lot of deprecated modules and predicates from `Stdlib.qll`, use API-graphs directly instead.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Additional data flow models for the builtin functions `map`, `filter`, `zip`, and `enumerate` have been added.
|
||||
6
python/ql/lib/change-notes/released/4.0.1.md
Normal file
6
python/ql/lib/change-notes/released/4.0.1.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 4.0.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed a bug in the extractor where a comment inside a subscript could sometimes cause the AST to be missing nodes.
|
||||
- Using the `break` and `continue` keywords outside of a loop, which is a syntax error but is accepted by our parser, would cause the control-flow construction to fail. This is now no longer the case.
|
||||
3
python/ql/lib/change-notes/released/4.0.2.md
Normal file
3
python/ql/lib/change-notes/released/4.0.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 4.0.2
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/lib/change-notes/released/4.0.3.md
Normal file
3
python/ql/lib/change-notes/released/4.0.3.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 4.0.3
|
||||
|
||||
No user-facing changes.
|
||||
9
python/ql/lib/change-notes/released/4.0.4.md
Normal file
9
python/ql/lib/change-notes/released/4.0.4.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 4.0.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- Added the methods `getMinArguments` and `getMaxArguments` to the `Function` class. These return the minimum and maximum positional arguments that the given function accepts.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- `MatchLiteralPattern`s such as `case None: ...` are now never pruned from the extracted source code. This fixes some situations where code was wrongly identified as unreachable.
|
||||
3
python/ql/lib/change-notes/released/4.0.5.md
Normal file
3
python/ql/lib/change-notes/released/4.0.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 4.0.5
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/lib/change-notes/released/4.0.6.md
Normal file
3
python/ql/lib/change-notes/released/4.0.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 4.0.6
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 3.1.0
|
||||
lastReleaseVersion: 4.0.6
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/python-all
|
||||
version: 3.1.1-dev
|
||||
version: 4.0.7-dev
|
||||
groups: python
|
||||
dbscheme: semmlecode.python.dbscheme
|
||||
extractor: python
|
||||
|
||||
@@ -181,7 +181,7 @@ module Path {
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node that checks that a path is safe to access. */
|
||||
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
|
||||
class SafeAccessCheck extends DataFlow::ExprNode {
|
||||
SafeAccessCheck() { this = DataFlow::BarrierGuard<safeAccessCheck/3>::getABarrierNode() }
|
||||
}
|
||||
@@ -192,7 +192,7 @@ module Path {
|
||||
|
||||
/** Provides a class for modeling new path safety checks. */
|
||||
module SafeAccessCheck {
|
||||
/** A data-flow node that checks that a path is safe to access. */
|
||||
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
|
||||
abstract class Range extends DataFlow::GuardNode {
|
||||
/** Holds if this guard validates `node` upon evaluating to `branch`. */
|
||||
abstract predicate checks(ControlFlowNode node, boolean branch);
|
||||
|
||||
@@ -746,6 +746,24 @@ class Guard extends Guard_ {
|
||||
override Expr getASubExpression() { result = this.getTest() }
|
||||
}
|
||||
|
||||
/** An annotation, such as the `int` part of `x: int` */
|
||||
class Annotation extends Expr {
|
||||
Annotation() {
|
||||
this = any(AnnAssign a).getAnnotation()
|
||||
or
|
||||
exists(Arguments args |
|
||||
this in [
|
||||
args.getAnAnnotation(),
|
||||
args.getAKwAnnotation(),
|
||||
args.getKwargannotation(),
|
||||
args.getVarargannotation()
|
||||
]
|
||||
)
|
||||
or
|
||||
this = any(FunctionExpr f).getReturns()
|
||||
}
|
||||
}
|
||||
|
||||
/* Expression Contexts */
|
||||
/** A context in which an expression used */
|
||||
class ExprContext extends ExprContext_ { }
|
||||
|
||||
@@ -163,6 +163,24 @@ class Function extends Function_, Scope, AstNode {
|
||||
ret.getValue() = result.getNode()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the minimum number of positional arguments that can be correctly passed to this function. */
|
||||
int getMinPositionalArguments() {
|
||||
result = count(this.getAnArg()) - count(this.getDefinition().getArgs().getADefault())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of positional arguments that can be correctly passed to this function.
|
||||
*
|
||||
* If the function has a `*vararg` parameter, there is no upper limit on the number of positional
|
||||
* arguments that can be passed to the function. In this case, this method returns a very large
|
||||
* number (currently `INT_MAX`, 2147483647, but this may change in the future).
|
||||
*/
|
||||
int getMaxPositionalArguments() {
|
||||
if exists(this.getVararg())
|
||||
then result = 2147483647 // INT_MAX
|
||||
else result = count(this.getAnArg())
|
||||
}
|
||||
}
|
||||
|
||||
/** A def statement. Note that FunctionDef extends Assign as a function definition binds the newly created function */
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
|
||||
*
|
||||
* This file acts as a wrapper for `internal.TypeTracker`, exposing some of the functionality with
|
||||
* names that are more appropriate for Python.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import internal.TypeTracker as Internal
|
||||
private import internal.TypeTrackerSpecific as InternalSpecific
|
||||
|
||||
/** A string that may appear as the name of an attribute or access path. */
|
||||
deprecated class AttributeName = InternalSpecific::TypeTrackerContent;
|
||||
|
||||
/** An attribute name, or the empty string (representing no attribute). */
|
||||
deprecated class OptionalAttributeName = InternalSpecific::OptionalTypeTrackerContent;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
|
||||
*
|
||||
* The summary of the steps needed to track a value to a given dataflow node.
|
||||
*
|
||||
* This can be used to track objects that implement a certain API in order to
|
||||
* recognize calls to that API. Note that type-tracking does not by itself provide a
|
||||
* source/sink relation, that is, it may determine that a node has a given type,
|
||||
* but it won't determine where that type came from.
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for tracking some type `myType`:
|
||||
* ```ql
|
||||
* DataFlow::TypeTrackingNode myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* result = myType(t2).track(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::LocalSourceNode myType() { myType(DataFlow::TypeTracker::end()) }
|
||||
* ```
|
||||
*
|
||||
* Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
|
||||
* `t = t2.step(myType(t2), result)`. If you additionally want to track individual
|
||||
* intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`.
|
||||
*/
|
||||
deprecated class TypeTracker extends Internal::TypeTracker {
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking, and the value starts in the attribute named `attrName`.
|
||||
* The type tracking only ends after the attribute has been loaded.
|
||||
*/
|
||||
predicate startInAttr(string attrName) { this.startInContent(attrName) }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Gets the attribute associated with this type tracker.
|
||||
*/
|
||||
string getAttr() { result = this.getContent() }
|
||||
}
|
||||
|
||||
deprecated module TypeTracker = Internal::TypeTracker;
|
||||
|
||||
deprecated class StepSummary = Internal::StepSummary;
|
||||
|
||||
deprecated module StepSummary = Internal::StepSummary;
|
||||
|
||||
deprecated class TypeBackTracker = Internal::TypeBackTracker;
|
||||
|
||||
deprecated module TypeBackTracker = Internal::TypeBackTracker;
|
||||
@@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
private import internal.TypeTrackingImpl as Impl
|
||||
import Impl::Shared::TypeTracking<Impl::TypeTrackingInput>
|
||||
private import semmle.python.Files
|
||||
import Impl::Shared::TypeTracking<Location, Impl::TypeTrackingInput>
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,957 +0,0 @@
|
||||
/** Step Summaries and Type Tracking */
|
||||
|
||||
private import TypeTrackerSpecific
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
cached
|
||||
deprecated newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
deprecated StoreStep(TypeTrackerContent content) {
|
||||
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
|
||||
basicStoreStep(_, _, dfc)
|
||||
)
|
||||
} or
|
||||
deprecated LoadStep(TypeTrackerContent content) {
|
||||
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
|
||||
basicLoadStep(_, _, dfc)
|
||||
)
|
||||
} or
|
||||
deprecated LoadStoreStep(TypeTrackerContent load, TypeTrackerContent store) {
|
||||
exists(DataFlowPublic::AttributeContent dfcLoad, DataFlowPublic::AttributeContent dfcStore |
|
||||
dfcLoad.getAttribute() = load and dfcStore.getAttribute() = store
|
||||
|
|
||||
basicLoadStoreStep(_, _, dfcLoad, dfcStore)
|
||||
)
|
||||
} or
|
||||
deprecated WithContent(ContentFilter filter) { basicWithContentStep(_, _, filter) } or
|
||||
deprecated WithoutContent(ContentFilter filter) { basicWithoutContentStep(_, _, filter) } or
|
||||
JumpStep()
|
||||
|
||||
cached
|
||||
deprecated newtype TTypeTracker =
|
||||
deprecated MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) {
|
||||
content = noContent()
|
||||
or
|
||||
// Restrict `content` to those that might eventually match a load.
|
||||
// We can't rely on `basicStoreStep` since `startInContent` might be used with
|
||||
// a content that has no corresponding store.
|
||||
exists(DataFlowPublic::AttributeContent loadContents |
|
||||
(
|
||||
basicLoadStep(_, _, loadContents)
|
||||
or
|
||||
basicLoadStoreStep(_, _, loadContents, _)
|
||||
) and
|
||||
compatibleContents(content, loadContents.getAttribute())
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
deprecated newtype TTypeBackTracker =
|
||||
deprecated MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) {
|
||||
content = noContent()
|
||||
or
|
||||
// As in MkTypeTracker, restrict `content` to those that might eventually match a store.
|
||||
exists(DataFlowPublic::AttributeContent storeContent |
|
||||
(
|
||||
basicStoreStep(_, _, storeContent)
|
||||
or
|
||||
basicLoadStoreStep(_, _, _, storeContent)
|
||||
) and
|
||||
compatibleContents(storeContent.getAttribute(), content)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a type tracker with no content and the call bit set to the given value. */
|
||||
cached
|
||||
deprecated TypeTracker noContentTypeTracker(boolean hasCall) {
|
||||
result = MkTypeTracker(hasCall, noContent())
|
||||
}
|
||||
|
||||
/** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */
|
||||
cached
|
||||
deprecated TypeTracker append(TypeTracker tt, StepSummary step) {
|
||||
exists(Boolean hasCall, OptionalTypeTrackerContent currentContents |
|
||||
tt = MkTypeTracker(hasCall, currentContents)
|
||||
|
|
||||
step = LevelStep() and result = tt
|
||||
or
|
||||
step = CallStep() and result = MkTypeTracker(true, currentContents)
|
||||
or
|
||||
step = ReturnStep() and hasCall = false and result = tt
|
||||
or
|
||||
step = JumpStep() and
|
||||
result = MkTypeTracker(false, currentContents)
|
||||
or
|
||||
exists(ContentFilter filter | result = tt |
|
||||
step = WithContent(filter) and
|
||||
currentContents = filter.getAMatchingContent()
|
||||
or
|
||||
step = WithoutContent(filter) and
|
||||
not currentContents = filter.getAMatchingContent()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(TypeTrackerContent storeContents, boolean hasCall |
|
||||
exists(TypeTrackerContent loadContents |
|
||||
step = LoadStep(pragma[only_bind_into](loadContents)) and
|
||||
tt = MkTypeTracker(hasCall, storeContents) and
|
||||
compatibleContents(storeContents, loadContents) and
|
||||
result = noContentTypeTracker(hasCall)
|
||||
)
|
||||
or
|
||||
step = StoreStep(pragma[only_bind_into](storeContents)) and
|
||||
tt = noContentTypeTracker(hasCall) and
|
||||
result = MkTypeTracker(hasCall, storeContents)
|
||||
)
|
||||
or
|
||||
exists(
|
||||
TypeTrackerContent currentContent, TypeTrackerContent store, TypeTrackerContent load,
|
||||
boolean hasCall
|
||||
|
|
||||
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
|
||||
compatibleContents(pragma[only_bind_into](currentContent), load) and
|
||||
tt = MkTypeTracker(pragma[only_bind_into](hasCall), currentContent) and
|
||||
result = MkTypeTracker(pragma[only_bind_out](hasCall), store)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
deprecated private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) {
|
||||
result = MkTypeBackTracker(hasReturn, noContent())
|
||||
}
|
||||
|
||||
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
|
||||
cached
|
||||
deprecated TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
|
||||
exists(Boolean hasReturn, OptionalTypeTrackerContent content |
|
||||
tbt = MkTypeBackTracker(hasReturn, content)
|
||||
|
|
||||
step = LevelStep() and result = tbt
|
||||
or
|
||||
step = CallStep() and hasReturn = false and result = tbt
|
||||
or
|
||||
step = ReturnStep() and result = MkTypeBackTracker(true, content)
|
||||
or
|
||||
step = JumpStep() and
|
||||
result = MkTypeBackTracker(false, content)
|
||||
or
|
||||
exists(ContentFilter filter | result = tbt |
|
||||
step = WithContent(filter) and
|
||||
content = filter.getAMatchingContent()
|
||||
or
|
||||
step = WithoutContent(filter) and
|
||||
not content = filter.getAMatchingContent()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(TypeTrackerContent loadContents, boolean hasReturn |
|
||||
exists(TypeTrackerContent storeContents |
|
||||
step = StoreStep(pragma[only_bind_into](storeContents)) and
|
||||
tbt = MkTypeBackTracker(hasReturn, loadContents) and
|
||||
compatibleContents(storeContents, loadContents) and
|
||||
result = noContentTypeBackTracker(hasReturn)
|
||||
)
|
||||
or
|
||||
step = LoadStep(pragma[only_bind_into](loadContents)) and
|
||||
tbt = noContentTypeBackTracker(hasReturn) and
|
||||
result = MkTypeBackTracker(hasReturn, loadContents)
|
||||
)
|
||||
or
|
||||
exists(
|
||||
TypeTrackerContent currentContent, TypeTrackerContent store, TypeTrackerContent load,
|
||||
boolean hasCall
|
||||
|
|
||||
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
|
||||
compatibleContents(store, pragma[only_bind_into](currentContent)) and
|
||||
tbt = MkTypeBackTracker(pragma[only_bind_into](hasCall), currentContent) and
|
||||
result = MkTypeBackTracker(pragma[only_bind_out](hasCall), load)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or intra-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* Steps contained in this predicate should _not_ depend on the call graph.
|
||||
*/
|
||||
cached
|
||||
deprecated predicate stepNoCall(
|
||||
TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(Node mid | nodeFrom.flowsTo(mid) and smallstepNoCall(mid, nodeTo, summary))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
cached
|
||||
deprecated predicate stepCall(
|
||||
TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(Node mid | nodeFrom.flowsTo(mid) and smallstepCall(mid, nodeTo, summary))
|
||||
}
|
||||
|
||||
cached
|
||||
deprecated predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
jumpStep(nodeFrom, nodeTo) and
|
||||
summary = JumpStep()
|
||||
or
|
||||
levelStepNoCall(nodeFrom, nodeTo) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
exists(TypeTrackerContent content |
|
||||
flowsToStoreStep(nodeFrom, nodeTo, content) and
|
||||
summary = StoreStep(content)
|
||||
or
|
||||
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
|
||||
basicLoadStep(nodeFrom, nodeTo, dfc)
|
||||
) and
|
||||
summary = LoadStep(content)
|
||||
)
|
||||
or
|
||||
exists(TypeTrackerContent loadContent, TypeTrackerContent storeContent |
|
||||
flowsToLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent) and
|
||||
summary = LoadStoreStep(loadContent, storeContent)
|
||||
)
|
||||
or
|
||||
exists(ContentFilter filter |
|
||||
basicWithContentStep(nodeFrom, nodeTo, filter) and
|
||||
summary = WithContent(filter)
|
||||
or
|
||||
basicWithoutContentStep(nodeFrom, nodeTo, filter) and
|
||||
summary = WithoutContent(filter)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
deprecated predicate smallstepCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
callStep(nodeFrom, nodeTo) and summary = CallStep()
|
||||
or
|
||||
returnStep(nodeFrom, nodeTo) and
|
||||
summary = ReturnStep()
|
||||
or
|
||||
levelStepCall(nodeFrom, nodeTo) and
|
||||
summary = LevelStep()
|
||||
}
|
||||
}
|
||||
|
||||
private import Cached
|
||||
|
||||
deprecated private predicate step(
|
||||
TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary
|
||||
) {
|
||||
stepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
deprecated private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||
step(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
deprecated private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
smallstepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
deprecated private predicate smallstepProj(Node nodeFrom, StepSummary summary) {
|
||||
smallstep(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` of the object in `nodeTo`.
|
||||
*
|
||||
* Note that `nodeTo` will always be a local source node that flows to the place where the content
|
||||
* is written in `basicStoreStep`. This may lead to the flow of information going "back in time"
|
||||
* from the point of view of the execution of the program.
|
||||
*
|
||||
* For instance, if we interpret attribute writes in Python as writing to content with the same
|
||||
* name as the attribute and consider the following snippet
|
||||
*
|
||||
* ```python
|
||||
* def foo(y):
|
||||
* x = Foo()
|
||||
* bar(x)
|
||||
* x.attr = y
|
||||
* baz(x)
|
||||
*
|
||||
* def bar(x):
|
||||
* z = x.attr
|
||||
* ```
|
||||
* for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`,
|
||||
* `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the
|
||||
* function. This means we will track the fact that `x.attr` can have the type of `y` into the
|
||||
* assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
|
||||
*/
|
||||
deprecated private predicate flowsToStoreStep(
|
||||
Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent content
|
||||
) {
|
||||
exists(Node obj |
|
||||
nodeTo.flowsTo(obj) and
|
||||
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
|
||||
basicStoreStep(nodeFrom, obj, dfc)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `loadContent` is loaded from `nodeFrom` and written to `storeContent` of `nodeTo`.
|
||||
*/
|
||||
deprecated private predicate flowsToLoadStoreStep(
|
||||
Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent loadContent,
|
||||
TypeTrackerContent storeContent
|
||||
) {
|
||||
exists(Node obj |
|
||||
nodeTo.flowsTo(obj) and
|
||||
exists(DataFlowPublic::AttributeContent loadDfc, DataFlowPublic::AttributeContent storeDfc |
|
||||
loadDfc.getAttribute() = loadContent and storeDfc.getAttribute() = storeContent
|
||||
|
|
||||
basicLoadStoreStep(nodeFrom, obj, loadDfc, storeDfc)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
|
||||
*
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
deprecated class StepSummary extends TStepSummary {
|
||||
/** Gets a textual representation of this step summary. */
|
||||
string toString() {
|
||||
this instanceof LevelStep and result = "level"
|
||||
or
|
||||
this instanceof CallStep and result = "call"
|
||||
or
|
||||
this instanceof ReturnStep and result = "return"
|
||||
or
|
||||
exists(TypeTrackerContent content | this = StoreStep(content) | result = "store " + content)
|
||||
or
|
||||
exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content)
|
||||
or
|
||||
exists(TypeTrackerContent load, TypeTrackerContent store |
|
||||
this = LoadStoreStep(load, store) and
|
||||
result = "load-store " + load + " -> " + store
|
||||
)
|
||||
or
|
||||
this instanceof JumpStep and result = "jump"
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for updating step summaries (`StepSummary`s). */
|
||||
deprecated module StepSummary {
|
||||
predicate append = Cached::append/2;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate stepCall = Cached::stepCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate stepNoCall = Cached::stepNoCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
stepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate smallstepNoCall = Cached::smallstepNoCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate smallstepCall = Cached::smallstepCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* Unlike `StepSummary::step`, this predicate does not compress
|
||||
* type-preserving steps.
|
||||
*/
|
||||
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
smallstepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
/** Gets the step summary for a level step. */
|
||||
StepSummary levelStep() { result = LevelStep() }
|
||||
|
||||
/** Gets the step summary for a call step. */
|
||||
StepSummary callStep() { result = CallStep() }
|
||||
|
||||
/** Gets the step summary for a return step. */
|
||||
StepSummary returnStep() { result = ReturnStep() }
|
||||
|
||||
/** Gets the step summary for storing into `content`. */
|
||||
StepSummary storeStep(TypeTrackerContent content) { result = StoreStep(content) }
|
||||
|
||||
/** Gets the step summary for loading from `content`. */
|
||||
StepSummary loadStep(TypeTrackerContent content) { result = LoadStep(content) }
|
||||
|
||||
/** Gets the step summary for loading from `load` and then storing into `store`. */
|
||||
StepSummary loadStoreStep(TypeTrackerContent load, TypeTrackerContent store) {
|
||||
result = LoadStoreStep(load, store)
|
||||
}
|
||||
|
||||
/** Gets the step summary for a step that only permits contents matched by `filter`. */
|
||||
StepSummary withContent(ContentFilter filter) { result = WithContent(filter) }
|
||||
|
||||
/** Gets the step summary for a step that blocks contents matched by `filter`. */
|
||||
StepSummary withoutContent(ContentFilter filter) { result = WithoutContent(filter) }
|
||||
|
||||
/** Gets the step summary for a jump step. */
|
||||
StepSummary jumpStep() { result = JumpStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
|
||||
*
|
||||
* A summary of the steps needed to track a value to a given dataflow node.
|
||||
*
|
||||
* This can be used to track objects that implement a certain API in order to
|
||||
* recognize calls to that API. Note that type-tracking does not by itself provide a
|
||||
* source/sink relation, that is, it may determine that a node has a given type,
|
||||
* but it won't determine where that type came from.
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for tracking some type `myType`:
|
||||
* ```ql
|
||||
* DataFlow::TypeTrackingNode myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* result = myType(t2).track(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
* ```
|
||||
*
|
||||
* Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
|
||||
* `t = t2.step(myType(t2), result)`. If you additionally want to track individual
|
||||
* intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`.
|
||||
*/
|
||||
deprecated class TypeTracker extends TTypeTracker {
|
||||
Boolean hasCall;
|
||||
OptionalTypeTrackerContent content;
|
||||
|
||||
TypeTracker() { this = MkTypeTracker(hasCall, content) }
|
||||
|
||||
/** Gets the summary resulting from appending `step` to this type-tracking summary. */
|
||||
TypeTracker append(StepSummary step) { result = append(this, step) }
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withCall, string withContent |
|
||||
(if hasCall = true then withCall = "with" else withCall = "without") and
|
||||
(
|
||||
if content != noContent()
|
||||
then withContent = " with content " + content
|
||||
else withContent = ""
|
||||
) and
|
||||
result = "type tracker " + withCall + " call steps" + withContent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasCall = false and content = noContent() }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
|
||||
* The type tracking only ends after the content has been loaded.
|
||||
*/
|
||||
predicate startInContent(TypeTrackerContent contentName) {
|
||||
hasCall = false and content = contentName
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking
|
||||
* when tracking a parameter into a call, but not out of it.
|
||||
*/
|
||||
predicate call() { hasCall = true and content = noContent() }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { content = noContent() }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been tracked into a call.
|
||||
*/
|
||||
boolean hasCall() { result = hasCall }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Gets the content associated with this type tracker.
|
||||
*/
|
||||
OptionalTypeTrackerContent getContent() { result = content }
|
||||
|
||||
/**
|
||||
* Gets a type tracker that starts where this one has left off to allow continued
|
||||
* tracking.
|
||||
*
|
||||
* This predicate is only defined if the type is not associated to a piece of content.
|
||||
*/
|
||||
TypeTracker continue() { content = noContent() and result = this }
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
bindingset[nodeFrom, this]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
stepProj(nodeFrom, summary) and
|
||||
result = this.append(summary) and
|
||||
step(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, this]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeTracker smallstepNoSimpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
smallstepProj(nodeFrom, summary) and
|
||||
result = this.append(summary) and
|
||||
smallstep(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* Unlike `TypeTracker::step`, this predicate exposes all edges
|
||||
* in the flow graph, and not just the edges between `Node`s.
|
||||
* It may therefore be less performant.
|
||||
*
|
||||
* Type tracking predicates using small steps typically take the following form:
|
||||
* ```ql
|
||||
* DataFlow::Node myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* t = t2.smallstep(myType(t2), result)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() {
|
||||
* result = myType(DataFlow::TypeTracker::end())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
result = this.smallstepNoSimpleLocalFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
result = this
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeTracker`s. */
|
||||
deprecated module TypeTracker {
|
||||
/**
|
||||
* Gets a valid end point of type tracking.
|
||||
*/
|
||||
TypeTracker end() { result.end() }
|
||||
|
||||
/**
|
||||
* INTERNAL USE ONLY.
|
||||
*
|
||||
* Gets a valid end point of type tracking with the call bit set to the given value.
|
||||
*/
|
||||
predicate end = Cached::noContentTypeTracker/1;
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
deprecated private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
step(_, nodeTo, summary)
|
||||
}
|
||||
|
||||
deprecated private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
smallstep(_, nodeTo, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
|
||||
*
|
||||
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
|
||||
*
|
||||
* This can for example be used to track callbacks that are passed to a certain API,
|
||||
* so we can model specific parameters of that callback as having a certain type.
|
||||
*
|
||||
* Note that type back-tracking does not provide a source/sink relation, that is,
|
||||
* it may determine that a node will be used in an API call somewhere, but it won't
|
||||
* determine exactly where that use was, or the path that led to the use.
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for back-tracking some callback type `myCallback`:
|
||||
*
|
||||
* ```ql
|
||||
* DataFlow::TypeTrackingNode myCallback(DataFlow::TypeBackTracker t) {
|
||||
* t.start() and
|
||||
* result = (< some API call >).getArgument(< n >).getALocalSource()
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* result = myCallback(t2).backtrack(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::TypeTrackingNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
|
||||
* ```
|
||||
*
|
||||
* Instead of `result = myCallback(t2).backtrack(t2, t)`, you can also use the equivalent
|
||||
* `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual
|
||||
* intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`.
|
||||
*/
|
||||
deprecated class TypeBackTracker extends TTypeBackTracker {
|
||||
Boolean hasReturn;
|
||||
OptionalTypeTrackerContent content;
|
||||
|
||||
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
|
||||
|
||||
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
|
||||
TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) }
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withReturn, string withContent |
|
||||
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
|
||||
(
|
||||
if content != noContent()
|
||||
then withContent = " with content " + content
|
||||
else withContent = ""
|
||||
) and
|
||||
result = "type back-tracker " + withReturn + " return steps" + withContent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasReturn = false and content = noContent() }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { content = noContent() }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been back-tracked into a call through return edge.
|
||||
*/
|
||||
boolean hasReturn() { result = hasReturn }
|
||||
|
||||
/**
|
||||
* Gets a type tracker that starts where this one has left off to allow continued
|
||||
* tracking.
|
||||
*
|
||||
* This predicate is only defined if the type has not been tracked into a piece of content.
|
||||
*/
|
||||
TypeBackTracker continue() { content = noContent() and result = this }
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a backwards
|
||||
* heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
|
||||
*/
|
||||
bindingset[nodeTo, result]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
backStepProj(nodeTo, summary) and
|
||||
this = result.prepend(summary) and
|
||||
step(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[nodeTo, result]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeBackTracker smallstepNoSimpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
backSmallstepProj(nodeTo, summary) and
|
||||
this = result.prepend(summary) and
|
||||
smallstep(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a backwards
|
||||
* local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
|
||||
*
|
||||
* Unlike `TypeBackTracker::step`, this predicate exposes all edges
|
||||
* in the flowgraph, and not just the edges between
|
||||
* `TypeTrackingNode`s. It may therefore be less performant.
|
||||
*
|
||||
* Type tracking predicates using small steps typically take the following form:
|
||||
* ```ql
|
||||
* DataFlow::Node myType(DataFlow::TypeBackTracker t) {
|
||||
* t.start() and
|
||||
* result = < some API call >.getArgument(< n >)
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* t = t2.smallstep(result, myType(t2))
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() {
|
||||
* result = myType(DataFlow::TypeBackTracker::end())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
this = this.smallstepNoSimpleLocalFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
this = result
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a forwards summary that is compatible with this backwards summary.
|
||||
* That is, if this summary describes the steps needed to back-track a value
|
||||
* from `sink` to `mid`, and the result is a valid summary of the steps needed
|
||||
* to track a value from `source` to `mid`, then the value from `source` may
|
||||
* also flow to `sink`.
|
||||
*/
|
||||
TypeTracker getACompatibleTypeTracker() {
|
||||
exists(boolean hasCall, OptionalTypeTrackerContent c |
|
||||
result = MkTypeTracker(hasCall, c) and
|
||||
(
|
||||
compatibleContents(c, content)
|
||||
or
|
||||
content = noContent() and c = content
|
||||
)
|
||||
|
|
||||
hasCall = false
|
||||
or
|
||||
this.hasReturn() = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeBackTracker`s. */
|
||||
deprecated module TypeBackTracker {
|
||||
/**
|
||||
* Gets a valid end point of type back-tracking.
|
||||
*/
|
||||
TypeBackTracker end() { result.end() }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides logic for constructing a call graph in mutual recursion with type tracking.
|
||||
*
|
||||
* When type tracking is used to construct a call graph, we cannot use the join-order
|
||||
* from `stepInlineLate`, because `step` becomes a recursive call, which means that we
|
||||
* will have a conjunct with 3 recursive calls: the call to `step`, the call to `stepProj`,
|
||||
* and the recursive type tracking call itself. The solution is to split the three-way
|
||||
* non-linear recursion into two non-linear predicates: one that first joins with the
|
||||
* projected `stepCall` relation, followed by a predicate that joins with the full
|
||||
* `stepCall` relation (`stepNoCall` not being recursive, can be join-ordered in the
|
||||
* same way as in `stepInlineLate`).
|
||||
*/
|
||||
deprecated module CallGraphConstruction {
|
||||
/** The input to call graph construction. */
|
||||
signature module InputSig {
|
||||
/** A state to track during type tracking. */
|
||||
class State;
|
||||
|
||||
/** Holds if type tracking should start at `start` in state `state`. */
|
||||
deprecated predicate start(Node start, State state);
|
||||
|
||||
/**
|
||||
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||
* which _does not_ depend on the call graph.
|
||||
*
|
||||
* Implementing this predicate using `StepSummary::[small]stepNoCall` yields
|
||||
* standard type tracking.
|
||||
*/
|
||||
deprecated predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||
|
||||
/**
|
||||
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||
* which _does_ depend on the call graph.
|
||||
*
|
||||
* Implementing this predicate using `StepSummary::[small]stepCall` yields
|
||||
* standard type tracking.
|
||||
*/
|
||||
deprecated predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||
|
||||
/** A projection of an element from the state space. */
|
||||
class StateProj;
|
||||
|
||||
/** Gets the projection of `state`. */
|
||||
StateProj stateProj(State state);
|
||||
|
||||
/** Holds if type tracking should stop at `n` when we are tracking projected state `stateProj`. */
|
||||
deprecated predicate filter(Node n, StateProj stateProj);
|
||||
}
|
||||
|
||||
/** Provides the `track` predicate for use in call graph construction. */
|
||||
module Make<InputSig Input> {
|
||||
pragma[nomagic]
|
||||
deprecated private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) {
|
||||
Input::stepNoCall(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
deprecated private predicate stepCallProj(Node nodeFrom, StepSummary summary) {
|
||||
Input::stepCall(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
deprecated private TypeTracker stepNoCallInlineLate(
|
||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
stepNoCallProj(nodeFrom, summary) and
|
||||
result = t.append(summary) and
|
||||
Input::stepNoCall(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[state]
|
||||
pragma[inline_late]
|
||||
private Input::StateProj stateProjInlineLate(Input::State state) {
|
||||
result = Input::stateProj(state)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
deprecated private Node track(Input::State state, TypeTracker t) {
|
||||
t.start() and Input::start(result, state)
|
||||
or
|
||||
exists(Input::StateProj stateProj |
|
||||
stateProj = stateProjInlineLate(state) and
|
||||
not Input::filter(result, stateProj)
|
||||
|
|
||||
exists(TypeTracker t2 | t = stepNoCallInlineLate(t2, track(state, t2), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
Input::stepCall(trackCall(state, t, summary), result, summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[t, summary]
|
||||
pragma[inline_late]
|
||||
deprecated private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) {
|
||||
result = t.append(summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
deprecated private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = track(state, t2) and
|
||||
stepCallProj(result, summary) and
|
||||
t = appendInlineLate(t2, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that can be reached from _some_ start node in state `state`. */
|
||||
pragma[nomagic]
|
||||
deprecated Node track(Input::State state) { result = track(state, TypeTracker::end()) }
|
||||
}
|
||||
|
||||
/** A simple version of `CallGraphConstruction` that uses standard type tracking. */
|
||||
module Simple {
|
||||
/** The input to call graph construction. */
|
||||
signature module InputSig {
|
||||
/** A state to track during type tracking. */
|
||||
class State;
|
||||
|
||||
/** Holds if type tracking should start at `start` in state `state`. */
|
||||
deprecated predicate start(Node start, State state);
|
||||
|
||||
/** Holds if type tracking should stop at `n`. */
|
||||
deprecated predicate filter(Node n);
|
||||
}
|
||||
|
||||
/** Provides the `track` predicate for use in call graph construction. */
|
||||
module Make<InputSig Input> {
|
||||
deprecated private module I implements CallGraphConstruction::InputSig {
|
||||
private import codeql.util.Unit
|
||||
|
||||
class State = Input::State;
|
||||
|
||||
predicate start(Node start, State state) { Input::start(start, state) }
|
||||
|
||||
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
StepSummary::stepNoCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
StepSummary::stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
class StateProj = Unit;
|
||||
|
||||
Unit stateProj(State state) { exists(state) and exists(result) }
|
||||
|
||||
predicate filter(Node n, Unit u) {
|
||||
Input::filter(n) and
|
||||
exists(u)
|
||||
}
|
||||
}
|
||||
|
||||
deprecated import CallGraphConstruction::Make<I>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* Provides Python-specific definitions for use in the type tracker library.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
|
||||
private import TypeTrackingImpl as TypeTrackingImpl
|
||||
|
||||
deprecated class Node = DataFlowPublic::Node;
|
||||
|
||||
deprecated class TypeTrackingNode = DataFlowPublic::TypeTrackingNode;
|
||||
|
||||
/** A content name for use by type trackers, or the empty string. */
|
||||
deprecated class OptionalTypeTrackerContent extends string {
|
||||
OptionalTypeTrackerContent() {
|
||||
this = ""
|
||||
or
|
||||
this = any(DataFlowPublic::AttributeContent dfc).getAttribute()
|
||||
}
|
||||
}
|
||||
|
||||
/** A content name for use by type trackers. */
|
||||
deprecated class TypeTrackerContent extends OptionalTypeTrackerContent {
|
||||
TypeTrackerContent() { this != "" }
|
||||
}
|
||||
|
||||
/** Gets the content string representing no value. */
|
||||
deprecated OptionalTypeTrackerContent noContent() { result = "" }
|
||||
|
||||
/**
|
||||
* A label to use for `WithContent` and `WithoutContent` steps, restricting
|
||||
* which `ContentSet` may pass through. Not currently used in Python.
|
||||
*/
|
||||
deprecated class ContentFilter extends Unit {
|
||||
TypeTrackerContent getAMatchingContent() { none() }
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
deprecated predicate compatibleContents(
|
||||
TypeTrackerContent storeContent, TypeTrackerContent loadContent
|
||||
) {
|
||||
storeContent = loadContent
|
||||
}
|
||||
|
||||
deprecated predicate simpleLocalFlowStep =
|
||||
TypeTrackingImpl::TypeTrackingInput::simpleLocalSmallStep/2;
|
||||
|
||||
deprecated predicate jumpStep = TypeTrackingImpl::TypeTrackingInput::jumpStep/2;
|
||||
|
||||
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */
|
||||
deprecated predicate levelStepCall(Node nodeFrom, Node nodeTo) { none() }
|
||||
|
||||
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
|
||||
deprecated predicate levelStepNoCall = TypeTrackingImpl::TypeTrackingInput::levelStepNoCall/2;
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call.
|
||||
*
|
||||
* Flow into summarized library methods is not included, as that will lead to negative
|
||||
* recursion (or, at best, terrible performance), since identifying calls to library
|
||||
* methods is done using API graphs (which uses type tracking).
|
||||
*/
|
||||
deprecated predicate callStep = TypeTrackingImpl::TypeTrackingInput::callStep/2;
|
||||
|
||||
/** Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. */
|
||||
deprecated predicate returnStep = TypeTrackingImpl::TypeTrackingInput::returnStep/2;
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
|
||||
*/
|
||||
deprecated predicate basicStoreStep = TypeTrackingImpl::TypeTrackingInput::storeStep/3;
|
||||
|
||||
/**
|
||||
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
|
||||
*/
|
||||
deprecated predicate basicLoadStep = TypeTrackingImpl::TypeTrackingInput::loadStep/3;
|
||||
|
||||
/**
|
||||
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
|
||||
*/
|
||||
deprecated predicate basicLoadStoreStep = TypeTrackingImpl::TypeTrackingInput::loadStoreStep/4;
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here.
|
||||
*/
|
||||
deprecated predicate basicWithoutContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`.
|
||||
*/
|
||||
deprecated predicate basicWithContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility class that is equivalent to `boolean` but does not require type joining.
|
||||
*/
|
||||
deprecated class Boolean extends boolean {
|
||||
Boolean() { this = true or this = false }
|
||||
}
|
||||
@@ -106,7 +106,7 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
|
||||
|
||||
private module TypeTrackerSummaryFlow = SummaryTypeTracker::SummaryFlow<SummaryTypeTrackerInput>;
|
||||
|
||||
module TypeTrackingInput implements Shared::TypeTrackingInput {
|
||||
module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
|
||||
class Node = DataFlowPublic::Node;
|
||||
|
||||
class LocalSourceNode = DataFlowPublic::LocalSourceNode;
|
||||
@@ -323,4 +323,4 @@ module TypeTrackingInput implements Shared::TypeTrackingInput {
|
||||
predicate nonStandardFlowsTo(LocalSourceNode localSource, Node dst) { localSource.flowsTo(dst) }
|
||||
}
|
||||
|
||||
import SharedImpl::TypeTracking<TypeTrackingInput>
|
||||
import SharedImpl::TypeTracking<Location, TypeTrackingInput>
|
||||
|
||||
@@ -1781,15 +1781,6 @@ module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/cgi.html.
|
||||
*/
|
||||
module FieldStorage {
|
||||
/**
|
||||
* DEPRECATED: Use `subclassRef` predicate instead.
|
||||
*
|
||||
* Gets a reference to the `cgi.FieldStorage` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = API::moduleImport("cgi").getMember("FieldStorage")
|
||||
}
|
||||
|
||||
/** Gets a reference to the `cgi.FieldStorage` class or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("cgi").getMember("FieldStorage").getASubclass*()
|
||||
@@ -1900,168 +1891,15 @@ module StdlibPrivate {
|
||||
// ---------------------------------------------------------------------------
|
||||
// BaseHTTPServer (Python 2 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `BaseHttpServer` module.
|
||||
*/
|
||||
deprecated API::Node baseHttpServer() { result = API::moduleImport("BaseHTTPServer") }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `BaseHttpServer` module.
|
||||
*/
|
||||
deprecated module BaseHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `BaseHTTPServer.BaseHTTPRequestHandler` class (Python 2 only).
|
||||
*/
|
||||
deprecated module BaseHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `BaseHttpServer.BaseHttpRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = baseHttpServer().getMember("BaseHTTPRequestHandler")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SimpleHTTPServer (Python 2 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `SimpleHttpServer` module.
|
||||
*/
|
||||
deprecated API::Node simpleHttpServer() { result = API::moduleImport("SimpleHTTPServer") }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `SimpleHttpServer` module.
|
||||
*/
|
||||
deprecated module SimpleHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `SimpleHTTPServer.SimpleHTTPRequestHandler` class (Python 2 only).
|
||||
*/
|
||||
deprecated module SimpleHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `SimpleHttpServer.SimpleHttpRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = simpleHttpServer().getMember("SimpleHTTPRequestHandler")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CGIHTTPServer (Python 2 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `CGIHTTPServer` module.
|
||||
*/
|
||||
deprecated API::Node cgiHttpServer() { result = API::moduleImport("CGIHTTPServer") }
|
||||
|
||||
/** Provides models for the `CGIHTTPServer` module. */
|
||||
deprecated module CgiHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `CGIHTTPServer.CGIHTTPRequestHandler` class (Python 2 only).
|
||||
*/
|
||||
deprecated module CgiHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `CGIHTTPServer.CgiHttpRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = cgiHttpServer().getMember("CGIHTTPRequestHandler")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// http (Python 3 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `http` module.
|
||||
*/
|
||||
deprecated API::Node http() { result = API::moduleImport("http") }
|
||||
|
||||
/** Provides models for the `http` module. */
|
||||
deprecated module StdlibHttp {
|
||||
// -------------------------------------------------------------------------
|
||||
// http.server
|
||||
// -------------------------------------------------------------------------
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `http.server` module.
|
||||
*/
|
||||
deprecated API::Node server() { result = http().getMember("server") }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server` module
|
||||
*/
|
||||
deprecated module Server {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server.BaseHTTPRequestHandler` class (Python 3 only).
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/http.server.html#http.server.BaseHTTPRequestHandler.
|
||||
*/
|
||||
deprecated module BaseHttpRequestHandler {
|
||||
/** Gets a reference to the `http.server.BaseHttpRequestHandler` class. */
|
||||
deprecated API::Node classRef() { result = server().getMember("BaseHTTPRequestHandler") }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server.SimpleHTTPRequestHandler` class (Python 3 only).
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/http.server.html#http.server.SimpleHTTPRequestHandler.
|
||||
*/
|
||||
deprecated module SimpleHttpRequestHandler {
|
||||
/** Gets a reference to the `http.server.SimpleHttpRequestHandler` class. */
|
||||
deprecated API::Node classRef() { result = server().getMember("SimpleHTTPRequestHandler") }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server.CGIHTTPRequestHandler` class (Python 3 only).
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/http.server.html#http.server.CGIHTTPRequestHandler.
|
||||
*/
|
||||
deprecated module CgiHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `http.server.CGIHTTPRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() { result = server().getMember("CGIHTTPRequestHandler") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `BaseHTTPRequestHandler` class and subclasses.
|
||||
*
|
||||
@@ -4523,6 +4361,124 @@ module StdlibPrivate {
|
||||
}
|
||||
}
|
||||
|
||||
/** A flow summary for `map`. */
|
||||
class MapSummary extends SummarizedCallable {
|
||||
MapSummary() { this = "builtins.map" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { result = API::builtin("map").getACall() }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() {
|
||||
result = API::builtin("map").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
exists(int i | exists(any(Call c).getArg(i)) |
|
||||
(
|
||||
input = "Argument[" + (i + 1).toString() + "].ListElement"
|
||||
or
|
||||
input = "Argument[" + (i + 1).toString() + "].SetElement"
|
||||
or
|
||||
// We reduce generality slightly by not tracking tuple contents on list arguments beyond the first, for performance.
|
||||
// TODO: Once we have TupleElementAny, this generality can be increased.
|
||||
i = 0 and
|
||||
exists(DataFlow::TupleElementContent tc, int j | j = tc.getIndex() |
|
||||
input = "Argument[1].TupleElement[" + j.toString() + "]"
|
||||
)
|
||||
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
|
||||
) and
|
||||
output = "Argument[0].Parameter[" + i.toString() + "]" and
|
||||
preservesValue = true
|
||||
)
|
||||
or
|
||||
input = "Argument[0].ReturnValue" and
|
||||
output = "ReturnValue.ListElement" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/** A flow summary for `filter`. */
|
||||
class FilterSummary extends SummarizedCallable {
|
||||
FilterSummary() { this = "builtins.filter" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { result = API::builtin("filter").getACall() }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() {
|
||||
result = API::builtin("filter").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[1].ListElement"
|
||||
or
|
||||
input = "Argument[1].SetElement"
|
||||
or
|
||||
exists(DataFlow::TupleElementContent tc, int i | i = tc.getIndex() |
|
||||
input = "Argument[1].TupleElement[" + i.toString() + "]"
|
||||
)
|
||||
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
|
||||
) and
|
||||
(output = "Argument[0].Parameter[0]" or output = "ReturnValue.ListElement") and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**A summary for `enumerate`. */
|
||||
class EnumerateSummary extends SummarizedCallable {
|
||||
EnumerateSummary() { this = "builtins.enumerate" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { result = API::builtin("enumerate").getACall() }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() {
|
||||
result = API::builtin("enumerate").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
input = "Argument[0].ListElement"
|
||||
or
|
||||
input = "Argument[0].SetElement"
|
||||
or
|
||||
exists(DataFlow::TupleElementContent tc, int i | i = tc.getIndex() |
|
||||
input = "Argument[0].TupleElement[" + i.toString() + "]"
|
||||
)
|
||||
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
|
||||
) and
|
||||
output = "ReturnValue.ListElement.TupleElement[1]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/** A flow summary for `zip`. */
|
||||
class ZipSummary extends SummarizedCallable {
|
||||
ZipSummary() { this = "builtins.zip" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { result = API::builtin("zip").getACall() }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() {
|
||||
result = API::builtin("zip").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
exists(int i | exists(any(Call c).getArg(i)) |
|
||||
(
|
||||
input = "Argument[" + i.toString() + "].ListElement"
|
||||
or
|
||||
input = "Argument[" + i.toString() + "].SetElement"
|
||||
or
|
||||
// We reduce generality slightly by not tracking tuple contents on arguments beyond the first two, for performance.
|
||||
// TODO: Once we have TupleElementAny, this generality can be increased.
|
||||
i in [0 .. 1] and
|
||||
exists(DataFlow::TupleElementContent tc, int j | j = tc.getIndex() |
|
||||
input = "Argument[" + i.toString() + "].TupleElement[" + j.toString() + "]"
|
||||
)
|
||||
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
|
||||
) and
|
||||
output = "ReturnValue.ListElement.TupleElement[" + i.toString() + "]" and
|
||||
preservesValue = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flow summaries for container methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -738,21 +738,9 @@ class PythonFunctionValue extends FunctionValue {
|
||||
else result = "function " + this.getQualifiedName()
|
||||
}
|
||||
|
||||
override int minParameters() {
|
||||
exists(Function f |
|
||||
f = this.getScope() and
|
||||
result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault())
|
||||
)
|
||||
}
|
||||
override int minParameters() { result = this.getScope().getMinPositionalArguments() }
|
||||
|
||||
override int maxParameters() {
|
||||
exists(Function f |
|
||||
f = this.getScope() and
|
||||
if exists(f.getVararg())
|
||||
then result = 2147483647 // INT_MAX
|
||||
else result = count(f.getAnArg())
|
||||
)
|
||||
}
|
||||
override int maxParameters() { result = this.getScope().getMaxPositionalArguments() }
|
||||
|
||||
/** Gets a control flow node corresponding to a return statement in this function */
|
||||
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }
|
||||
|
||||
@@ -21,6 +21,8 @@ private module CleartextLoggingConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "Clear-text logging of sensitive information" vulnerabilities. */
|
||||
|
||||
@@ -21,6 +21,8 @@ private module CleartextStorageConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "Clear-text storage of sensitive information" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "code injection" vulnerabilities. */
|
||||
|
||||
@@ -20,6 +20,8 @@ module CommandInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "command injection" vulnerabilities. */
|
||||
|
||||
@@ -20,6 +20,8 @@ module CookieInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "cookie injection" vulnerabilities. */
|
||||
|
||||
@@ -16,6 +16,8 @@ private module HeaderInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "HTTP Header injection" vulnerabilities. */
|
||||
|
||||
@@ -19,6 +19,8 @@ private module LdapInjectionDnConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof DnSink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof DnSanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "LDAP injection via the distinguished name (DN) parameter" vulnerabilities. */
|
||||
@@ -30,6 +32,8 @@ private module LdapInjectionFilterConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof FilterSink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof FilterSanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "LDAP injection via the filter parameter" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module LogInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "log injection" vulnerabilities. */
|
||||
|
||||
@@ -56,6 +56,8 @@ module NoSqlInjectionConfig implements DataFlow::StateConfigSig {
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
module NoSqlInjectionFlow = TaintTracking::GlobalWithState<NoSqlInjectionConfig>;
|
||||
|
||||
@@ -31,6 +31,8 @@ private module PamAuthorizationConfig implements DataFlow::ConfigSig {
|
||||
// Flow from handle to the authenticate call in the final step
|
||||
exists(VulnPamAuthCall c | c.getArg(0) = node1 | node2 = c)
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "PAM Authorization" vulnerabilities. */
|
||||
|
||||
@@ -71,6 +71,8 @@ module PathInjectionConfig implements DataFlow::StateConfigSig {
|
||||
stateFrom instanceof NotNormalized and
|
||||
stateTo instanceof NormalizedUnchecked
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "path injection" vulnerabilities. */
|
||||
|
||||
@@ -35,6 +35,11 @@ module PolynomialReDoS {
|
||||
/** Gets the regex that is being executed by this node. */
|
||||
abstract RegExpTerm getRegExp();
|
||||
|
||||
/** Gets a term within the regexp that may perform polynomial back-tracking. */
|
||||
final PolynomialBackTrackingTerm getABacktrackingTerm() {
|
||||
result.getRootTerm() = this.getRegExp()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node to highlight in the alert message.
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,24 @@ private module PolynomialReDoSConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
// Diff-informed incremental mode is currently disabled for this query due to
|
||||
// API limitations. The query exposes sink.getABacktrackingTerm() as an alert
|
||||
// location, but there is no way to express that information through
|
||||
// getASelectedSinkLocation() because there is no @location in the CodeQL
|
||||
// database that corresponds to a term inside a regular expression. As a
|
||||
// result, this query could miss alerts in diff-informed incremental mode.
|
||||
//
|
||||
// To address this problem, we need to have a version of
|
||||
// getASelectedSinkLocation() that uses hasLocationInfo() instead of
|
||||
// returning Location objects.
|
||||
predicate observeDiffInformedIncrementalMode() { none() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getHighlight().getLocation()
|
||||
or
|
||||
result = sink.(Sink).getABacktrackingTerm().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "polynomial regular expression denial of service (ReDoS)" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module ReflectedXssConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "reflected server-side cross-site scripting" vulnerabilities. */
|
||||
|
||||
@@ -18,6 +18,14 @@ private module RegexInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getRegexExecution().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "regular expression injection" vulnerabilities. */
|
||||
|
||||
@@ -29,6 +29,12 @@ private module FullServerSideRequestForgeryConfig implements DataFlow::ConfigSig
|
||||
or
|
||||
node instanceof FullUrlControlSanitizer
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() {
|
||||
// The partial request forgery query depends on `fullyControlledRequest` to reject alerts about
|
||||
// such full-controlled requests, regardless of the associated source.
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +64,13 @@ private module PartialServerSideRequestForgeryConfig implements DataFlow::Config
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
// Note: this query does not select the sink itself
|
||||
result = sink.(Sink).getRequest().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,8 @@ private module SqlInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "SQL injection" vulnerabilities. */
|
||||
|
||||
@@ -26,6 +26,8 @@ private module StackTraceExposureConfig implements DataFlow::ConfigSig {
|
||||
nodeTo = attr
|
||||
)
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "stack trace exposure" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module TarSlipConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "tar slip" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module TemplateInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node node) { node instanceof Sink }
|
||||
|
||||
predicate isBarrierIn(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "template injection" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module UnsafeDeserializationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "code execution from deserialization" vulnerabilities. */
|
||||
|
||||
@@ -28,6 +28,16 @@ module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigSig {
|
||||
|
||||
// override to require the path doesn't have unmatched return steps
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
|
||||
Location getASelectedSinkLocation(DataFlow::Node sink) {
|
||||
result = sink.(Sink).getLocation()
|
||||
or
|
||||
result = sink.(Sink).getStringConstruction().getLocation()
|
||||
or
|
||||
result = sink.(Sink).getCommandExecution().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "shell command constructed from library input" vulnerabilities. */
|
||||
|
||||
@@ -32,6 +32,8 @@ private module UrlRedirectConfig implements DataFlow::StateConfigSig {
|
||||
) {
|
||||
any(UrlRedirect::AdditionalFlowStep a).step(nodeFrom, stateFrom, nodeTo, stateTo)
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "URL redirection" vulnerabilities. */
|
||||
|
||||
@@ -33,6 +33,8 @@ module NormalHashFunction {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
sensitiveDataExtraStepForCalls(node1, node2)
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on sensitive data" vulnerabilities. */
|
||||
@@ -63,6 +65,8 @@ module ComputationallyExpensiveHashFunction {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
sensitiveDataExtraStepForCalls(node1, node2)
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on passwords" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module XmlBombConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "XML bomb" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module XpathInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "Xpath Injection" vulnerabilities. */
|
||||
|
||||
@@ -17,6 +17,8 @@ private module XxeConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "XML External Entity (XXE)" vulnerabilities. */
|
||||
|
||||
@@ -62,7 +62,7 @@ module HeuristicNames {
|
||||
*/
|
||||
string maybeAccountInfo() {
|
||||
result = "(?is).*acc(ou)?nt.*" or
|
||||
result = "(?is).*(puid|username|userid|session(id|key)).*" or
|
||||
result = "(?is).*(puid|user.?name|user.?id|session.?(id|key)).*" or
|
||||
result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*"
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ module HeuristicNames {
|
||||
* a password or an authorization key.
|
||||
*/
|
||||
string maybePassword() {
|
||||
result = "(?is).*pass(wd|word|code|phrase)(?!.*question).*" or
|
||||
result = "(?is).*(auth(entication|ori[sz]ation)?)key.*"
|
||||
result = "(?is).*pass(wd|word|code|.?phrase)(?!.*question).*" or
|
||||
result = "(?is).*(auth(entication|ori[sz]ation)?).?key.*"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
## 1.4.8
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.4.7
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- The `py/mixed-tuple-returns` query no longer flags instances where the tuple is passed into the function as an argument, as this led to too many false positives.
|
||||
|
||||
## 1.4.6
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- The `py/special-method-wrong-signature` has been modernized and rewritten to no longer rely on outdated APIs. Moreover, the query no longer flags cases where a default value is never used, as these alerts were rarely useful.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- The `py/unused-global-variable` now no longer flags variables that are only used in forward references (e.g. the `Foo` in `def bar(x: "Foo"): ...`).
|
||||
|
||||
## 1.4.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.4.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.4.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.4.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.4.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### New Queries
|
||||
|
||||
108
python/ql/src/Functions/MethodArgNames.qll
Normal file
108
python/ql/src/Functions/MethodArgNames.qll
Normal file
@@ -0,0 +1,108 @@
|
||||
/** Definitions for reasoning about the expected first argument names for methods. */
|
||||
|
||||
import python
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import DataFlow
|
||||
|
||||
/** Holds if `f` is a method of the class `c`. */
|
||||
private predicate methodOfClass(Function f, Class c) {
|
||||
exists(FunctionDef d | d.getDefinedFunction() = f and d.getScope() = c)
|
||||
}
|
||||
|
||||
/** Holds if `c` is a metaclass. */
|
||||
private predicate isMetaclass(Class c) {
|
||||
c = API::builtin("type").getASubclass*().asSource().asExpr().(ClassExpr).getInnerScope()
|
||||
}
|
||||
|
||||
/** Holds if `c` is a Zope interface. */
|
||||
private predicate isZopeInterface(Class c) {
|
||||
c =
|
||||
API::moduleImport("zope")
|
||||
.getMember("interface")
|
||||
.getMember("Interface")
|
||||
.getASubclass*()
|
||||
.asSource()
|
||||
.asExpr()
|
||||
.(ClassExpr)
|
||||
.getInnerScope()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is used in the initialisation of `c`.
|
||||
* This means `f` isn't being used as a normal method.
|
||||
* Ideally it should be a `@staticmethod`; however this wasn't possible prior to Python 3.10.
|
||||
* We exclude this case from the `not-named-self` query.
|
||||
* However there is potential for a new query that specifically covers and alerts for this case.
|
||||
*/
|
||||
private predicate usedInInit(Function f, Class c) {
|
||||
methodOfClass(f, c) and
|
||||
exists(Call call |
|
||||
call.getScope() = c and
|
||||
DataFlow::localFlow(DataFlow::exprNode(f.getDefinition()), DataFlow::exprNode(call.getFunc()))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` has no arguments, and also has a decorator.
|
||||
* We assume that the decorator affect the method in such a way that a `self` parameter is unneeded.
|
||||
*/
|
||||
private predicate noArgsWithDecorator(Function f) {
|
||||
not exists(f.getAnArg()) and
|
||||
exists(f.getADecorator())
|
||||
}
|
||||
|
||||
/** Holds if the first parameter of `f` should be named `self`. */
|
||||
predicate shouldBeSelf(Function f, Class c) {
|
||||
methodOfClass(f, c) and
|
||||
not isStaticmethod(f) and
|
||||
not isClassmethod(f) and
|
||||
not isMetaclass(c) and
|
||||
not isZopeInterface(c) and
|
||||
not usedInInit(f, c) and
|
||||
not noArgsWithDecorator(f)
|
||||
}
|
||||
|
||||
/** Holds if the first parameter of `f` should be named `cls`. */
|
||||
predicate shouldBeCls(Function f, Class c) {
|
||||
methodOfClass(f, c) and
|
||||
not isStaticmethod(f) and
|
||||
(
|
||||
isClassmethod(f) and not isMetaclass(c)
|
||||
or
|
||||
isMetaclass(c) and not isClassmethod(f)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the first parameter of `f` is named `self`. */
|
||||
predicate firstArgNamedSelf(Function f) { f.getArgName(0) = "self" }
|
||||
|
||||
/** Holds if the first parameter of `f` refers to the class - it is either named `cls`, or it is named `self` and is a method of a metaclass. */
|
||||
predicate firstArgRefersToCls(Function f, Class c) {
|
||||
methodOfClass(f, c) and
|
||||
exists(string argname | argname = f.getArgName(0) |
|
||||
argname = "cls"
|
||||
or
|
||||
/* Not PEP8, but relatively common */
|
||||
argname = "mcls"
|
||||
or
|
||||
/* If c is a metaclass, allow arguments named `self`. */
|
||||
argname = "self" and
|
||||
isMetaclass(c)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the first parameter of `f` should be named `self`, but isn't. */
|
||||
predicate firstArgShouldBeNamedSelfAndIsnt(Function f) {
|
||||
shouldBeSelf(f, _) and
|
||||
not firstArgNamedSelf(f)
|
||||
}
|
||||
|
||||
/** Holds if the first parameter of `f` should be named `cls`, but isn't. */
|
||||
predicate firstArgShouldReferToClsAndDoesnt(Function f) {
|
||||
exists(Class c |
|
||||
methodOfClass(f, c) and
|
||||
shouldBeCls(f, c) and
|
||||
not firstArgRefersToCls(f, c)
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
class Entry(object):
|
||||
@classmethod
|
||||
def make(klass):
|
||||
def make(self):
|
||||
return Entry()
|
||||
|
||||
@@ -5,20 +5,19 @@
|
||||
|
||||
|
||||
<overview>
|
||||
<p> The first parameter of a class method, a new method or any metaclass method
|
||||
should be called <code>cls</code>. This makes the purpose of the parameter clear to other developers.
|
||||
<p> The first parameter of a class method (including certain special methods such as <code>__new__</code>), or a method of a metaclass,
|
||||
should be named <code>cls</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Change the name of the first parameter to <code>cls</code> as recommended by the style guidelines
|
||||
<p>Ensure that the first parameter of class methods is named <code>cls</code>, as recommended by the style guidelines
|
||||
in PEP 8.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the example, the first parameter to <code>make()</code> is <code>klass</code> which should be changed to <code>cls</code>
|
||||
for ease of comprehension.
|
||||
<p>In the following example, the first parameter of the class method <code>make</code> is named <code>self</code> instead of <code>cls</code>.
|
||||
</p>
|
||||
|
||||
<sample src="NonCls.py" />
|
||||
@@ -29,6 +28,7 @@ for ease of comprehension.
|
||||
|
||||
<li>Python PEP 8: <a href="http://www.python.org/dev/peps/pep-0008/#function-and-method-arguments">Function and method arguments</a>.</li>
|
||||
<li>Python Tutorial: <a href="http://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
|
||||
<li>Python Docs: <a href="https://docs.python.org/3/library/functions.html#classmethod">classmethod</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @name First parameter of a class method is not named 'cls'
|
||||
* @description Using an alternative name for the first parameter of a class method makes code more
|
||||
* difficult to read; PEP8 states that the first parameter to class methods should be 'cls'.
|
||||
* @description By the PEP8 style guide, the first parameter of a class method should be named `cls`.
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* readability
|
||||
@@ -13,30 +12,11 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate first_arg_cls(Function f) {
|
||||
exists(string argname | argname = f.getArgName(0) |
|
||||
argname = "cls"
|
||||
or
|
||||
/* Not PEP8, but relatively common */
|
||||
argname = "mcls"
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_type_method(Function f) {
|
||||
exists(ClassValue c | c.getScope() = f.getScope() and c.getASuperType() = ClassValue::type())
|
||||
}
|
||||
|
||||
predicate classmethod_decorators_only(Function f) {
|
||||
forall(Expr decorator | decorator = f.getADecorator() | decorator.(Name).getId() = "classmethod")
|
||||
}
|
||||
import MethodArgNames
|
||||
|
||||
from Function f, string message
|
||||
where
|
||||
(f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
|
||||
not first_arg_cls(f) and
|
||||
classmethod_decorators_only(f) and
|
||||
not f.getName() = "__new__" and
|
||||
firstArgShouldReferToClsAndDoesnt(f) and
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class Point:
|
||||
def __init__(val, x, y): # first parameter is mis-named 'val'
|
||||
def __init__(val, x, y): # BAD: first parameter is mis-named 'val'
|
||||
val._x = x
|
||||
val._y = y
|
||||
|
||||
class Point2:
|
||||
def __init__(self, x, y): # first parameter is correctly named 'self'
|
||||
def __init__(self, x, y): # GOOD: first parameter is correctly named 'self'
|
||||
self._x = x
|
||||
self._y = y
|
||||
@@ -6,22 +6,18 @@
|
||||
|
||||
<overview>
|
||||
<p> Normal methods should have at least one parameter and the first parameter should be called <code>self</code>.
|
||||
This makes the purpose of the parameter clear to other developers.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>If there is at least one parameter, then change the name of the first parameter to <code>self</code> as recommended by the style guidelines
|
||||
<p>Ensure that the first parameter of a normal method is named <code>self</code>, as recommended by the style guidelines
|
||||
in PEP 8.</p>
|
||||
<p>If there are no parameters, then it cannot be a normal method. It may need to be marked as a <code>staticmethod</code>
|
||||
or it could be moved out of the class as a normal function.
|
||||
<p>If a <code>self</code> parameter is unneeded, the method should be decorated with <code>staticmethod</code>, or moved out of the class as a regular function.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following methods can both be used to assign values to variables in a <code>point</code>
|
||||
object. The second method makes the association clearer because the <code>self</code> parameter is
|
||||
used.</p>
|
||||
<p>In the following cases, the first argument of <code>Point.__init__</code> is named <code>val</code> instead; whereas in <code>Point2.__init__</code> it is correctly named <code>self</code>.</p>
|
||||
<sample src="NonSelf.py" />
|
||||
|
||||
|
||||
@@ -31,7 +27,7 @@ used.</p>
|
||||
<li>Python PEP 8: <a href="http://www.python.org/dev/peps/pep-0008/#function-and-method-arguments">Function and
|
||||
method arguments</a>.</li>
|
||||
<li>Python Tutorial: <a href="http://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
|
||||
|
||||
<li>Python Docs: <a href="https://docs.python.org/3/library/functions.html#staticmethod">staticmethod</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/**
|
||||
* @name First parameter of a method is not named 'self'
|
||||
* @description Using an alternative name for the first parameter of an instance method makes
|
||||
* code more difficult to read; PEP8 states that the first parameter to instance
|
||||
* methods should be 'self'.
|
||||
* @description By the PEP8 style guide, the first parameter of a normal method should be named `self`.
|
||||
* @kind problem
|
||||
* @tags maintainability
|
||||
* readability
|
||||
@@ -14,45 +12,19 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.libraries.Zope
|
||||
import MethodArgNames
|
||||
|
||||
predicate is_type_method(FunctionValue fv) {
|
||||
exists(ClassValue c | c.declaredAttribute(_) = fv and c.getASuperType() = ClassValue::type())
|
||||
}
|
||||
|
||||
predicate used_in_defining_scope(FunctionValue fv) {
|
||||
exists(Call c | c.getScope() = fv.getScope().getScope() and c.getFunc().pointsTo(fv))
|
||||
}
|
||||
|
||||
from Function f, FunctionValue fv, string message
|
||||
from Function f, string message
|
||||
where
|
||||
exists(ClassValue cls, string name |
|
||||
cls.declaredAttribute(name) = fv and
|
||||
cls.isNewStyle() and
|
||||
not name = "__new__" and
|
||||
not name = "__metaclass__" and
|
||||
not name = "__init_subclass__" and
|
||||
not name = "__class_getitem__" and
|
||||
/* declared in scope */
|
||||
f.getScope() = cls.getScope()
|
||||
) and
|
||||
not f.getArgName(0) = "self" and
|
||||
not is_type_method(fv) and
|
||||
fv.getScope() = f and
|
||||
not f.getName() = "lambda" and
|
||||
not used_in_defining_scope(fv) and
|
||||
firstArgShouldBeNamedSelfAndIsnt(f) and
|
||||
(
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message =
|
||||
"Normal methods should have 'self', rather than '" + f.getArgName(0) +
|
||||
"', as their first parameter."
|
||||
else
|
||||
message =
|
||||
"Normal methods should have at least one parameter (the first of which should be 'self')."
|
||||
) and
|
||||
not f.hasVarArg()
|
||||
) and
|
||||
not fv instanceof ZopeInterfaceMethodValue
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message =
|
||||
"Normal methods should have 'self', rather than '" + f.getArgName(0) +
|
||||
"', as their first parameter."
|
||||
else
|
||||
message =
|
||||
"Normal methods should have at least one parameter (the first of which should be 'self')."
|
||||
)
|
||||
select f, message
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
* quality
|
||||
* @problem.severity recommendation
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
@@ -11,13 +12,15 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
predicate returns_tuple_of_size(Function func, int size, AstNode origin) {
|
||||
exists(Return return, TupleValue val |
|
||||
predicate returns_tuple_of_size(Function func, int size, Tuple tuple) {
|
||||
exists(Return return, DataFlow::Node value |
|
||||
value.asExpr() = return.getValue() and
|
||||
return.getScope() = func and
|
||||
return.getValue().pointsTo(val, origin)
|
||||
any(DataFlow::LocalSourceNode n | n.asExpr() = tuple).flowsTo(value)
|
||||
|
|
||||
size = val.length()
|
||||
size = count(int n | exists(tuple.getElt(n)))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +28,8 @@ from Function func, int s1, int s2, AstNode t1, AstNode t2
|
||||
where
|
||||
returns_tuple_of_size(func, s1, t1) and
|
||||
returns_tuple_of_size(func, s2, t2) and
|
||||
s1 < s2
|
||||
s1 < s2 and
|
||||
// Don't report on functions that have a return type annotation
|
||||
not exists(func.getDefinition().(FunctionExpr).getReturns())
|
||||
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2,
|
||||
"tuple of size " + s2
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @kind problem
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* quality
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
@@ -11,12 +12,15 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.internal.DataFlowDispatch as DD
|
||||
|
||||
predicate is_unary_op(string name) {
|
||||
name in [
|
||||
"__del__", "__repr__", "__neg__", "__pos__", "__abs__", "__invert__", "__complex__",
|
||||
"__int__", "__float__", "__long__", "__oct__", "__hex__", "__str__", "__index__", "__enter__",
|
||||
"__hash__", "__bool__", "__nonzero__", "__unicode__", "__len__", "__iter__", "__reversed__"
|
||||
"__hash__", "__bool__", "__nonzero__", "__unicode__", "__len__", "__iter__", "__reversed__",
|
||||
"__aenter__", "__aiter__", "__anext__", "__await__", "__ceil__", "__floor__", "__trunc__",
|
||||
"__length_hint__", "__dir__", "__bytes__"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,91 +32,138 @@ predicate is_binary_op(string name) {
|
||||
"__and__", "__xor__", "__or__", "__ne__", "__radd__", "__rsub__", "__rmul__", "__rfloordiv__",
|
||||
"__rdiv__", "__rtruediv__", "__rmod__", "__rdivmod__", "__rpow__", "__rlshift__", "__gt__",
|
||||
"__rrshift__", "__rand__", "__rxor__", "__ror__", "__iadd__", "__isub__", "__imul__",
|
||||
"__ifloordiv__", "__idiv__", "__itruediv__", "__ge__", "__imod__", "__idivmod__", "__ipow__",
|
||||
"__ilshift__", "__irshift__", "__iand__", "__ixor__", "__ior__", "__coerce__", "__cmp__",
|
||||
"__rcmp__", "__getattr___", "__getattribute___"
|
||||
"__ifloordiv__", "__idiv__", "__itruediv__", "__ge__", "__imod__", "__ipow__", "__ilshift__",
|
||||
"__irshift__", "__iand__", "__ixor__", "__ior__", "__coerce__", "__cmp__", "__rcmp__",
|
||||
"__getattr__", "__getattribute__", "__buffer__", "__release_buffer__", "__matmul__",
|
||||
"__rmatmul__", "__imatmul__", "__missing__", "__class_getitem__", "__mro_entries__",
|
||||
"__format__"
|
||||
]
|
||||
}
|
||||
|
||||
predicate is_ternary_op(string name) {
|
||||
name in ["__setattr__", "__set__", "__setitem__", "__getslice__", "__delslice__"]
|
||||
name in ["__setattr__", "__set__", "__setitem__", "__getslice__", "__delslice__", "__set_name__"]
|
||||
}
|
||||
|
||||
predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" }
|
||||
predicate is_quad_op(string name) { name in ["__setslice__", "__exit__", "__aexit__"] }
|
||||
|
||||
int argument_count(PythonFunctionValue f, string name, ClassValue cls) {
|
||||
cls.declaredAttribute(name) = f and
|
||||
(
|
||||
is_unary_op(name) and result = 1
|
||||
or
|
||||
is_binary_op(name) and result = 2
|
||||
or
|
||||
is_ternary_op(name) and result = 3
|
||||
or
|
||||
is_quad_op(name) and result = 4
|
||||
)
|
||||
int argument_count(string name) {
|
||||
is_unary_op(name) and result = 1
|
||||
or
|
||||
is_binary_op(name) and result = 2
|
||||
or
|
||||
is_ternary_op(name) and result = 3
|
||||
or
|
||||
is_quad_op(name) and result = 4
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 if `func` is a static method, and 0 otherwise. This predicate is used to adjust the
|
||||
* number of expected arguments for a special method accordingly.
|
||||
*/
|
||||
int staticmethod_correction(Function func) {
|
||||
if DD::isStaticmethod(func) then result = 1 else result = 0
|
||||
}
|
||||
|
||||
predicate incorrect_special_method_defn(
|
||||
PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner
|
||||
Function func, string message, boolean show_counts, string name, boolean is_unused_default
|
||||
) {
|
||||
exists(int required | required = argument_count(func, name, owner) |
|
||||
exists(int required, int correction |
|
||||
required = argument_count(name) - correction and correction = staticmethod_correction(func)
|
||||
|
|
||||
/* actual_non_default <= actual */
|
||||
if required > func.maxParameters()
|
||||
then message = "Too few parameters" and show_counts = true
|
||||
if required > func.getMaxPositionalArguments()
|
||||
then message = "Too few parameters" and show_counts = true and is_unused_default = false
|
||||
else
|
||||
if required < func.minParameters()
|
||||
then message = "Too many parameters" and show_counts = true
|
||||
if required < func.getMinPositionalArguments()
|
||||
then message = "Too many parameters" and show_counts = true and is_unused_default = false
|
||||
else (
|
||||
func.minParameters() < required and
|
||||
not func.getScope().hasVarArg() and
|
||||
message = (required - func.minParameters()) + " default values(s) will never be used" and
|
||||
show_counts = false
|
||||
func.getMinPositionalArguments() < required and
|
||||
not func.hasVarArg() and
|
||||
message =
|
||||
(required - func.getMinPositionalArguments()) + " default values(s) will never be used" and
|
||||
show_counts = false and
|
||||
is_unused_default = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
|
||||
owner.declaredAttribute("__pow__") = func and
|
||||
(
|
||||
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
|
||||
predicate incorrect_pow(
|
||||
Function func, string message, boolean show_counts, boolean is_unused_default
|
||||
) {
|
||||
exists(int correction | correction = staticmethod_correction(func) |
|
||||
func.getMaxPositionalArguments() < 2 - correction and
|
||||
message = "Too few parameters" and
|
||||
show_counts = true and
|
||||
is_unused_default = false
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
func.getMinPositionalArguments() > 3 - correction and
|
||||
message = "Too many parameters" and
|
||||
show_counts = true and
|
||||
is_unused_default = false
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
func.getMinPositionalArguments() < 2 - correction and
|
||||
message = (2 - func.getMinPositionalArguments()) + " default value(s) will never be used" and
|
||||
show_counts = false and
|
||||
is_unused_default = true
|
||||
or
|
||||
func.minParameters() = 3 and
|
||||
func.getMinPositionalArguments() = 3 - correction and
|
||||
message = "Third parameter to __pow__ should have a default value" and
|
||||
show_counts = false
|
||||
show_counts = false and
|
||||
is_unused_default = false
|
||||
)
|
||||
}
|
||||
|
||||
predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
|
||||
owner.declaredAttribute("__get__") = func and
|
||||
(
|
||||
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
|
||||
predicate incorrect_round(
|
||||
Function func, string message, boolean show_counts, boolean is_unused_default
|
||||
) {
|
||||
exists(int correction | correction = staticmethod_correction(func) |
|
||||
func.getMaxPositionalArguments() < 1 - correction and
|
||||
message = "Too few parameters" and
|
||||
show_counts = true and
|
||||
is_unused_default = false
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
func.getMinPositionalArguments() > 2 - correction and
|
||||
message = "Too many parameters" and
|
||||
show_counts = true and
|
||||
is_unused_default = false
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
not func.getScope().hasVarArg() and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
func.getMinPositionalArguments() = 2 - correction and
|
||||
message = "Second parameter to __round__ should have a default value" and
|
||||
show_counts = false and
|
||||
is_unused_default = false
|
||||
)
|
||||
}
|
||||
|
||||
string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) {
|
||||
exists(int i | i = argument_count(f, name, owner) | result = i.toString())
|
||||
or
|
||||
owner.declaredAttribute(name) = f and
|
||||
(name = "__get__" or name = "__pow__") and
|
||||
result = "2 or 3"
|
||||
predicate incorrect_get(
|
||||
Function func, string message, boolean show_counts, boolean is_unused_default
|
||||
) {
|
||||
exists(int correction | correction = staticmethod_correction(func) |
|
||||
func.getMaxPositionalArguments() < 3 - correction and
|
||||
message = "Too few parameters" and
|
||||
show_counts = true and
|
||||
is_unused_default = false
|
||||
or
|
||||
func.getMinPositionalArguments() > 3 - correction and
|
||||
message = "Too many parameters" and
|
||||
show_counts = true and
|
||||
is_unused_default = false
|
||||
or
|
||||
func.getMinPositionalArguments() < 2 - correction and
|
||||
not func.hasVarArg() and
|
||||
message = (2 - func.getMinPositionalArguments()) + " default value(s) will never be used" and
|
||||
show_counts = false and
|
||||
is_unused_default = true
|
||||
)
|
||||
}
|
||||
|
||||
string has_parameters(PythonFunctionValue f) {
|
||||
exists(int i | i = f.minParameters() |
|
||||
string should_have_parameters(string name) {
|
||||
if name in ["__pow__", "__get__"]
|
||||
then result = "2 or 3"
|
||||
else result = argument_count(name).toString()
|
||||
}
|
||||
|
||||
string has_parameters(Function f) {
|
||||
exists(int i | i = f.getMinPositionalArguments() |
|
||||
i = 0 and result = "no parameters"
|
||||
or
|
||||
i = 1 and result = "1 parameter"
|
||||
@@ -121,23 +172,44 @@ string has_parameters(PythonFunctionValue f) {
|
||||
)
|
||||
}
|
||||
|
||||
from
|
||||
PythonFunctionValue f, string message, string sizes, boolean show_counts, string name,
|
||||
ClassValue owner
|
||||
where
|
||||
/** Holds if `f` is likely to be a placeholder, and hence not interesting enough to report. */
|
||||
predicate isLikelyPlaceholderFunction(Function f) {
|
||||
// Body has only a single statement.
|
||||
f.getBody().getItem(0) = f.getBody().getLastItem() and
|
||||
(
|
||||
incorrect_special_method_defn(f, message, show_counts, name, owner)
|
||||
// Body is a string literal. This is a common pattern for Zope interfaces.
|
||||
f.getBody().getLastItem().(ExprStmt).getValue() instanceof StringLiteral
|
||||
or
|
||||
incorrect_pow(f, message, show_counts, owner) and name = "__pow__"
|
||||
// Body just raises an exception.
|
||||
f.getBody().getLastItem() instanceof Raise
|
||||
or
|
||||
incorrect_get(f, message, show_counts, owner) and name = "__get__"
|
||||
// Body is a pass statement.
|
||||
f.getBody().getLastItem() instanceof Pass
|
||||
)
|
||||
}
|
||||
|
||||
from
|
||||
Function f, string message, string sizes, boolean show_counts, string name, Class owner,
|
||||
boolean show_unused_defaults
|
||||
where
|
||||
owner.getAMethod() = f and
|
||||
f.getName() = name and
|
||||
(
|
||||
incorrect_special_method_defn(f, message, show_counts, name, show_unused_defaults)
|
||||
or
|
||||
incorrect_pow(f, message, show_counts, show_unused_defaults) and name = "__pow__"
|
||||
or
|
||||
incorrect_get(f, message, show_counts, show_unused_defaults) and name = "__get__"
|
||||
or
|
||||
incorrect_round(f, message, show_counts, show_unused_defaults) and
|
||||
name = "__round__"
|
||||
) and
|
||||
not isLikelyPlaceholderFunction(f) and
|
||||
show_unused_defaults = false and
|
||||
(
|
||||
show_counts = false and sizes = ""
|
||||
or
|
||||
show_counts = true and
|
||||
sizes =
|
||||
", which has " + has_parameters(f) + ", but should have " +
|
||||
should_have_parameters(f, name, owner)
|
||||
sizes = ", which has " + has_parameters(f) + ", but should have " + should_have_parameters(name)
|
||||
)
|
||||
select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName()
|
||||
|
||||
108
python/ql/src/Metrics/Internal/TypeAnnotations.ql
Normal file
108
python/ql/src/Metrics/Internal/TypeAnnotations.ql
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @name Type metrics
|
||||
* @description Counts of various kinds of type annotations in Python code.
|
||||
* @kind table
|
||||
* @id py/type-metrics
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
class BuiltinType extends Name {
|
||||
BuiltinType() { this.getId() in ["int", "float", "str", "bool", "bytes", "None"] }
|
||||
}
|
||||
|
||||
newtype TAnnotatable =
|
||||
TAnnotatedFunction(FunctionExpr f) { exists(f.getReturns()) } or
|
||||
TAnnotatedParameter(Parameter p) { exists(p.getAnnotation()) } or
|
||||
TAnnotatedAssignment(AnnAssign a) { exists(a.getAnnotation()) }
|
||||
|
||||
abstract class Annotatable extends TAnnotatable {
|
||||
string toString() { result = "Annotatable" }
|
||||
|
||||
abstract Expr getAnnotation();
|
||||
}
|
||||
|
||||
class AnnotatedFunction extends TAnnotatedFunction, Annotatable {
|
||||
FunctionExpr function;
|
||||
|
||||
AnnotatedFunction() { this = TAnnotatedFunction(function) }
|
||||
|
||||
override Expr getAnnotation() { result = function.getReturns() }
|
||||
}
|
||||
|
||||
class AnnotatedParameter extends TAnnotatedParameter, Annotatable {
|
||||
Parameter parameter;
|
||||
|
||||
AnnotatedParameter() { this = TAnnotatedParameter(parameter) }
|
||||
|
||||
override Expr getAnnotation() { result = parameter.getAnnotation() }
|
||||
}
|
||||
|
||||
class AnnotatedAssignment extends TAnnotatedAssignment, Annotatable {
|
||||
AnnAssign assignment;
|
||||
|
||||
AnnotatedAssignment() { this = TAnnotatedAssignment(assignment) }
|
||||
|
||||
override Expr getAnnotation() { result = assignment.getAnnotation() }
|
||||
}
|
||||
|
||||
/** Holds if `e` is a forward declaration of a type. */
|
||||
predicate is_forward_declaration(Expr e) { e instanceof StringLiteral }
|
||||
|
||||
/** Holds if `e` is a type that may be difficult to analyze. */
|
||||
predicate is_complex_type(Expr e) {
|
||||
e instanceof Subscript and not is_optional_type(e)
|
||||
or
|
||||
e instanceof Tuple
|
||||
or
|
||||
e instanceof List
|
||||
}
|
||||
|
||||
/** Holds if `e` is a type of the form `Optional[...]`. */
|
||||
predicate is_optional_type(Subscript e) { e.getObject().(Name).getId() = "Optional" }
|
||||
|
||||
/** Holds if `e` is a simple type, that is either an identifier (excluding built-in types) or an attribute of a simple type. */
|
||||
predicate is_simple_type(Expr e) {
|
||||
e instanceof Name and not e instanceof BuiltinType
|
||||
or
|
||||
is_simple_type(e.(Attribute).getObject())
|
||||
}
|
||||
|
||||
/** Holds if `e` is a built-in type. */
|
||||
predicate is_builtin_type(Expr e) { e instanceof BuiltinType }
|
||||
|
||||
predicate type_count(
|
||||
string kind, int total, int built_in_count, int forward_declaration_count, int simple_type_count,
|
||||
int complex_type_count, int optional_type_count
|
||||
) {
|
||||
kind = "Parameter annotation" and
|
||||
total = count(AnnotatedParameter p) and
|
||||
built_in_count = count(AnnotatedParameter p | is_builtin_type(p.getAnnotation())) and
|
||||
forward_declaration_count =
|
||||
count(AnnotatedParameter p | is_forward_declaration(p.getAnnotation())) and
|
||||
simple_type_count = count(AnnotatedParameter p | is_simple_type(p.getAnnotation())) and
|
||||
complex_type_count = count(AnnotatedParameter p | is_complex_type(p.getAnnotation())) and
|
||||
optional_type_count = count(AnnotatedParameter p | is_optional_type(p.getAnnotation()))
|
||||
or
|
||||
kind = "Return type annotation" and
|
||||
total = count(AnnotatedFunction f) and
|
||||
built_in_count = count(AnnotatedFunction f | is_builtin_type(f.getAnnotation())) and
|
||||
forward_declaration_count = count(AnnotatedFunction f | is_forward_declaration(f.getAnnotation())) and
|
||||
simple_type_count = count(AnnotatedFunction f | is_simple_type(f.getAnnotation())) and
|
||||
complex_type_count = count(AnnotatedFunction f | is_complex_type(f.getAnnotation())) and
|
||||
optional_type_count = count(AnnotatedFunction f | is_optional_type(f.getAnnotation()))
|
||||
or
|
||||
kind = "Annotated assignment" and
|
||||
total = count(AnnotatedAssignment a) and
|
||||
built_in_count = count(AnnotatedAssignment a | is_builtin_type(a.getAnnotation())) and
|
||||
forward_declaration_count =
|
||||
count(AnnotatedAssignment a | is_forward_declaration(a.getAnnotation())) and
|
||||
simple_type_count = count(AnnotatedAssignment a | is_simple_type(a.getAnnotation())) and
|
||||
complex_type_count = count(AnnotatedAssignment a | is_complex_type(a.getAnnotation())) and
|
||||
optional_type_count = count(AnnotatedAssignment a | is_optional_type(a.getAnnotation()))
|
||||
}
|
||||
|
||||
from
|
||||
string message, int total, int built_in, int forward_decl, int simple, int complex, int optional
|
||||
where type_count(message, total, built_in, forward_decl, simple, complex, optional)
|
||||
select message, total, built_in, forward_decl, simple, complex, optional
|
||||
@@ -1,15 +0,0 @@
|
||||
f = open("filename")
|
||||
... # Actions to perform on file
|
||||
f.close()
|
||||
# File only closed if actions are completed successfully
|
||||
|
||||
with open("filename") as f:
|
||||
...# Actions to perform on file
|
||||
# File always closed
|
||||
|
||||
f = open("filename")
|
||||
try:
|
||||
... # Actions to perform on file
|
||||
finally:
|
||||
f.close()
|
||||
# File always closed
|
||||
@@ -4,32 +4,30 @@
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p> If a file is opened then it should always be closed again, even if an
|
||||
exception is raised.
|
||||
Failing to ensure that all files are closed may result in failure due to too
|
||||
many open files.</p>
|
||||
|
||||
<p>When a file is opened, it should always be closed.
|
||||
</p>
|
||||
<p>A file opened for writing that is not closed when the application exits may result in data loss, where not all of the data written may be saved to the file.
|
||||
A file opened for reading or writing that is not closed may also use up file descriptors, which is a resource leak that in long running applications could lead to a failure to open additional files.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Ensure that if you open a file it is always closed on exiting the method.
|
||||
Wrap the code between the <code>open()</code> and <code>close()</code>
|
||||
functions in a <code>with</code> statement or use a <code>try...finally</code>
|
||||
statement. Using a <code>with</code> statement is preferred as it is shorter
|
||||
and more readable.</p>
|
||||
<p>Ensure that opened files are always closed, including when an exception could be raised.
|
||||
The best practice is often to use a <code>with</code> statement to automatically clean up resources.
|
||||
Otherwise, ensure that <code>.close()</code> is called in a <code>try...except</code> or <code>try...finally</code>
|
||||
block to handle any possible exceptions.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following code shows examples of different ways of closing a file. In the first example, the
|
||||
file is closed only if the method is exited successfully. In the other examples, the file is always
|
||||
closed on exiting the method.</p>
|
||||
<p>In the following examples, in the case marked BAD, the file may not be closed if an exception is raised. In the cases marked GOOD, the file is always closed.</p>
|
||||
|
||||
<sample src="FileNotAlwaysClosed.py" />
|
||||
<sample src="examples/FileNotAlwaysClosed.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Python Documentation: <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files">Reading and writing files</a>.</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/reference/compound_stmts.html#the-with-statement">The with statement</a>,
|
||||
<a href="http://docs.python.org/reference/compound_stmts.html#the-try-statement">The try statement</a>.</li>
|
||||
<li>Python PEP 343: <a href="http://www.python.org/dev/peps/pep-0343">The "with" Statement</a>.</li>
|
||||
|
||||
@@ -1,74 +1,26 @@
|
||||
/**
|
||||
* @name File is not always closed
|
||||
* @description Opening a file without ensuring that it is always closed may cause resource leaks.
|
||||
* @description Opening a file without ensuring that it is always closed may lead to data loss or resource leaks.
|
||||
* @kind problem
|
||||
* @tags efficiency
|
||||
* correctness
|
||||
* resources
|
||||
* quality
|
||||
* external/cwe/cwe-772
|
||||
* @problem.severity warning
|
||||
* @sub-severity high
|
||||
* @precision medium
|
||||
* @precision high
|
||||
* @id py/file-not-closed
|
||||
*/
|
||||
|
||||
import python
|
||||
import FileOpen
|
||||
import FileNotAlwaysClosedQuery
|
||||
|
||||
/**
|
||||
* Whether resource is opened and closed in in a matched pair of methods,
|
||||
* either `__enter__` and `__exit__` or `__init__` and `__del__`
|
||||
*/
|
||||
predicate opened_in_enter_closed_in_exit(ControlFlowNode open) {
|
||||
file_not_closed_at_scope_exit(open) and
|
||||
exists(FunctionValue entry, FunctionValue exit |
|
||||
open.getScope() = entry.getScope() and
|
||||
exists(ClassValue cls |
|
||||
cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit
|
||||
or
|
||||
cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit
|
||||
) and
|
||||
exists(AttrNode attr_open, AttrNode attrclose |
|
||||
attr_open.getScope() = entry.getScope() and
|
||||
attrclose.getScope() = exit.getScope() and
|
||||
expr_is_open(attr_open.(DefinitionNode).getValue(), open) and
|
||||
attr_open.getName() = attrclose.getName() and
|
||||
close_method_call(_, attrclose)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate file_not_closed_at_scope_exit(ControlFlowNode open) {
|
||||
exists(EssaVariable v |
|
||||
BaseFlow::reaches_exit(v) and
|
||||
var_is_open(v, open) and
|
||||
not file_is_returned(v, open)
|
||||
)
|
||||
or
|
||||
call_to_open(open) and
|
||||
not exists(AssignmentDefinition def | def.getValue() = open) and
|
||||
not exists(Return r | r.getValue() = open.getNode())
|
||||
}
|
||||
|
||||
predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) {
|
||||
exists(EssaVariable v |
|
||||
exit.(RaisingNode).viableExceptionalExit(_, _) and
|
||||
not closes_arg(exit, v.getSourceVariable()) and
|
||||
not close_method_call(exit, v.getAUse().(NameNode)) and
|
||||
var_is_open(v, open) and
|
||||
v.getAUse() = exit.getAChild*()
|
||||
)
|
||||
}
|
||||
|
||||
/* Check to see if a file is opened but not closed or returned */
|
||||
from ControlFlowNode defn, string message
|
||||
from FileOpen fo, string msg
|
||||
where
|
||||
not opened_in_enter_closed_in_exit(defn) and
|
||||
(
|
||||
file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed."
|
||||
or
|
||||
not file_not_closed_at_scope_exit(defn) and
|
||||
file_not_closed_at_exception_exit(defn, _) and
|
||||
message = "File may not be closed if an exception is raised."
|
||||
)
|
||||
select defn.getNode(), message
|
||||
fileNotClosed(fo) and
|
||||
msg = "File is opened but is not closed."
|
||||
or
|
||||
fileMayNotBeClosedOnException(fo, _) and
|
||||
msg = "File may not be closed if an exception is raised."
|
||||
select fo, msg
|
||||
|
||||
165
python/ql/src/Resources/FileNotAlwaysClosedQuery.qll
Normal file
165
python/ql/src/Resources/FileNotAlwaysClosedQuery.qll
Normal file
@@ -0,0 +1,165 @@
|
||||
/** Definitions for reasoning about whether files are closed. */
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
/** A CFG node where a file is opened. */
|
||||
abstract class FileOpenSource extends DataFlow::CfgNode { }
|
||||
|
||||
/** A call to the builtin `open` or `os.open`. */
|
||||
class FileOpenCall extends FileOpenSource {
|
||||
FileOpenCall() {
|
||||
this = [API::builtin("open").getACall(), API::moduleImport("os").getMember("open").getACall()]
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::TypeTrackingNode fileOpenInstance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof FileOpenSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = fileOpenInstance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that returns an instance of an open file object.
|
||||
* This includes calls to methods that transitively call `open` or similar.
|
||||
*/
|
||||
class FileOpen extends DataFlow::CallCfgNode {
|
||||
FileOpen() { fileOpenInstance(DataFlow::TypeTracker::end()).flowsTo(this) }
|
||||
}
|
||||
|
||||
/** A call that may wrap a file object in a wrapper class or `os.fdopen`. */
|
||||
class FileWrapperCall extends DataFlow::CallCfgNode {
|
||||
DataFlow::Node wrapped;
|
||||
|
||||
FileWrapperCall() {
|
||||
wrapped = this.getArg(_).getALocalSource() and
|
||||
this.getFunction() = classTracker(_)
|
||||
or
|
||||
wrapped = this.getArg(0) and
|
||||
this = API::moduleImport("os").getMember("fdopen").getACall()
|
||||
or
|
||||
wrapped = this.getArg(0) and
|
||||
this = API::moduleImport("django").getMember("http").getMember("FileResponse").getACall()
|
||||
}
|
||||
|
||||
/** Gets the file that this call wraps. */
|
||||
DataFlow::Node getWrapped() { result = wrapped }
|
||||
}
|
||||
|
||||
/** A node where a file is closed. */
|
||||
abstract class FileClose extends DataFlow::CfgNode {
|
||||
/** Holds if this file close will occur if an exception is thrown at `raises`. */
|
||||
predicate guardsExceptions(DataFlow::CfgNode raises) {
|
||||
cfgGetASuccessorStar(raises.asCfgNode().getAnExceptionalSuccessor(), this.asCfgNode())
|
||||
or
|
||||
// The expression is after the close call.
|
||||
// This also covers the body of a `with` statement.
|
||||
cfgGetASuccessorStar(this.asCfgNode(), raises.asCfgNode())
|
||||
}
|
||||
}
|
||||
|
||||
private predicate cfgGetASuccessor(ControlFlowNode src, ControlFlowNode sink) {
|
||||
sink = src.getASuccessor()
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
private predicate cfgGetASuccessorPlus(ControlFlowNode src, ControlFlowNode sink) =
|
||||
fastTC(cfgGetASuccessor/2)(src, sink)
|
||||
|
||||
pragma[inline]
|
||||
private predicate cfgGetASuccessorStar(ControlFlowNode src, ControlFlowNode sink) {
|
||||
src = sink
|
||||
or
|
||||
cfgGetASuccessorPlus(src, sink)
|
||||
}
|
||||
|
||||
/** A call to the `.close()` method of a file object. */
|
||||
class FileCloseCall extends FileClose {
|
||||
FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) }
|
||||
}
|
||||
|
||||
/** A call to `os.close`. */
|
||||
class OsCloseCall extends FileClose {
|
||||
OsCloseCall() { this = API::moduleImport("os").getMember("close").getACall().getArg(0) }
|
||||
}
|
||||
|
||||
/** A `with` statement. */
|
||||
class WithStatement extends FileClose {
|
||||
WithStatement() { this.asExpr() = any(With w).getContextExpr() }
|
||||
}
|
||||
|
||||
/** Holds if an exception may be raised at `raises` if `file` is a file object. */
|
||||
private predicate mayRaiseWithFile(DataFlow::CfgNode file, DataFlow::CfgNode raises) {
|
||||
// Currently just consider any method called on `file`; e.g. `file.write()`; as potentially raising an exception
|
||||
raises.(DataFlow::MethodCallNode).getObject() = file and
|
||||
not file instanceof FileOpen and
|
||||
not file instanceof FileClose
|
||||
}
|
||||
|
||||
/** Holds if data flows from `nodeFrom` to `nodeTo` in one step that also includes file wrapper classes. */
|
||||
private predicate fileAdditionalLocalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(FileWrapperCall fw | nodeFrom = fw.getWrapped() and nodeTo = fw)
|
||||
}
|
||||
|
||||
private predicate fileLocalFlowHelper0(
|
||||
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
exists(DataFlow::Node nodeMid |
|
||||
nodeFrom.flowsTo(nodeMid) and fileAdditionalLocalFlowStep(nodeMid, nodeTo)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate fileLocalFlowHelper1(
|
||||
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
fileLocalFlowHelper0*(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/** Holds if data flows from `source` to `sink`, including file wrapper classes. */
|
||||
pragma[inline]
|
||||
private predicate fileLocalFlow(FileOpen source, DataFlow::Node sink) {
|
||||
exists(DataFlow::LocalSourceNode mid | fileLocalFlowHelper1(source, mid) and mid.flowsTo(sink))
|
||||
}
|
||||
|
||||
/** Holds if the file opened at `fo` is closed. */
|
||||
predicate fileIsClosed(FileOpen fo) { exists(FileClose fc | fileLocalFlow(fo, fc)) }
|
||||
|
||||
/** Holds if the file opened at `fo` is returned to the caller. This makes the caller responsible for closing the file. */
|
||||
predicate fileIsReturned(FileOpen fo) {
|
||||
exists(Return ret, Expr retVal |
|
||||
(
|
||||
retVal = ret.getValue()
|
||||
or
|
||||
retVal = ret.getValue().(List).getAnElt()
|
||||
or
|
||||
retVal = ret.getValue().(Tuple).getAnElt()
|
||||
) and
|
||||
fileLocalFlow(fo, DataFlow::exprNode(retVal))
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the file opened at `fo` is stored in a field. We assume that another method is then responsible for closing the file. */
|
||||
predicate fileIsStoredInField(FileOpen fo) {
|
||||
exists(DataFlow::AttrWrite aw | fileLocalFlow(fo, aw.getValue()))
|
||||
}
|
||||
|
||||
/** Holds if the file opened at `fo` is not closed, and is expected to be closed. */
|
||||
predicate fileNotClosed(FileOpen fo) {
|
||||
not fileIsClosed(fo) and
|
||||
not fileIsReturned(fo) and
|
||||
not fileIsStoredInField(fo)
|
||||
}
|
||||
|
||||
predicate fileMayNotBeClosedOnException(FileOpen fo, DataFlow::Node raises) {
|
||||
fileIsClosed(fo) and
|
||||
exists(DataFlow::CfgNode fileRaised |
|
||||
mayRaiseWithFile(fileRaised, raises) and
|
||||
fileLocalFlow(fo, fileRaised) and
|
||||
not exists(FileClose fc |
|
||||
fileLocalFlow(fo, fc) and
|
||||
fc.guardsExceptions(raises)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
/** Contains predicates concerning when and where files are opened and closed. */
|
||||
/**
|
||||
* DEPRECATED: Use FileNotAlwaysClosedQuery instead.
|
||||
* Contains predicates concerning when and where files are opened and closed.
|
||||
*/
|
||||
deprecated module;
|
||||
|
||||
import python
|
||||
import semmle.python.pointsto.Filters
|
||||
|
||||
17
python/ql/src/Resources/examples/FileNotAlwaysClosed.py
Normal file
17
python/ql/src/Resources/examples/FileNotAlwaysClosed.py
Normal file
@@ -0,0 +1,17 @@
|
||||
def bad():
|
||||
f = open("filename", "w")
|
||||
f.write("could raise exception") # BAD: This call could raise an exception, leading to the file not being closed.
|
||||
f.close()
|
||||
|
||||
|
||||
def good1():
|
||||
with open("filename", "w") as f:
|
||||
f.write("always closed") # GOOD: The `with` statement ensures the file is always closed.
|
||||
|
||||
def good2():
|
||||
f = open("filename", "w")
|
||||
try:
|
||||
f.write("always closed")
|
||||
finally:
|
||||
f.close() # GOOD: The `finally` block always ensures the file is closed.
|
||||
|
||||
@@ -171,6 +171,10 @@ private module UntrustedDataToExternalApiConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() {
|
||||
none() // Not used for PR analysis
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
|
||||
|
||||
@@ -110,6 +110,10 @@ module InsecureContextConfiguration implements DataFlow::StateConfigSig {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() {
|
||||
none() // Too complicated, but might be possible after some refactoring.
|
||||
}
|
||||
}
|
||||
|
||||
private module InsecureContextFlow = DataFlow::GlobalWithState<InsecureContextConfiguration>;
|
||||
|
||||
@@ -23,7 +23,7 @@ from
|
||||
where
|
||||
PolynomialReDoSFlow::flowPath(source, sink) and
|
||||
sinkNode = sink.getNode() and
|
||||
regexp.getRootTerm() = sinkNode.getRegExp()
|
||||
regexp = sinkNode.getABacktrackingTerm()
|
||||
// not (
|
||||
// source.getNode().(Source).getKind() = "url" and
|
||||
// regexp.isAtEndLine()
|
||||
|
||||
@@ -119,6 +119,8 @@ private module HardcodedCredentialsConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
module HardcodedCredentialsFlow = TaintTracking::Global<HardcodedCredentialsConfig>;
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
<recommendation>
|
||||
|
||||
<p>To guard against SSRF attacks you should avoid putting user-provided input directly
|
||||
into a request URL. Instead, either maintain a list of authorized URLs on the server and choose
|
||||
from that list based on the input provided, or perform proper validation of the input.
|
||||
into a request URL. On the application level, maintain a list of authorized URLs on the server and choose
|
||||
from that list based on the input provided. If that is not possible, one should verify the IP address for all user-controlled
|
||||
requests to ensure they are not private. This requires saving the verified IP address of each domain,
|
||||
then utilizing a custom HTTP adapter to ensure that future requests to that domain use the verified IP address.
|
||||
On the network level, you can segment the vulnerable application into its own LAN or block access to specific devices.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
#Make a list of functions to increment their arguments by 0 to 9.
|
||||
def make_incrementers():
|
||||
result = []
|
||||
for i in range(10):
|
||||
def incrementer(x):
|
||||
return x + i
|
||||
result.append(incrementer)
|
||||
return result
|
||||
|
||||
#This will fail
|
||||
def test():
|
||||
incs = make_incrementers()
|
||||
for x in range(10):
|
||||
for y in range(10):
|
||||
assert incs[x](y) == x+y
|
||||
|
||||
test()
|
||||
@@ -1,60 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Nested functions are a useful feature of Python as it allows a function to access the variables of its enclosing function.
|
||||
However, the programmer needs to be aware that when an inner function accesses a variable in an outer scope,
|
||||
it is the variable that is captured, not the value of that variable.
|
||||
</p>
|
||||
<p>
|
||||
Therefore, care must be taken when the captured variable is a loop variable, since it is the loop <em>variable</em> and
|
||||
<em>not</em> the <em>value</em> of that variable that is captured.
|
||||
This will mean that by the time that the inner function executes,
|
||||
the loop variable will have its final value, not the value when the inner function was created.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
The simplest way to fix this problem is to add a local variable of the same name as the outer variable and initialize that
|
||||
using the outer variable as a default.
|
||||
<code>
|
||||
for var in seq:
|
||||
...
|
||||
def inner_func(arg):
|
||||
...
|
||||
use(var)
|
||||
</code>
|
||||
becomes
|
||||
<code>
|
||||
for var in seq:
|
||||
...
|
||||
def inner_func(arg, var=var):
|
||||
...
|
||||
use(var)
|
||||
</code>
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In this example, a list of functions is created which should each increment its argument by its index in the list.
|
||||
However, since <code>i</code> will be 9 when the functions execute, they will each increment their argument by 9.
|
||||
</p>
|
||||
<sample src="LoopVariableCapture.py" />
|
||||
<p>
|
||||
This can be fixed by adding the default value as shown below. The default value is computed when the function is created, so the desired effect is achieved.
|
||||
</p>
|
||||
|
||||
<sample src="LoopVariableCapture2.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>The Hitchhiker’s Guide to Python: <a href="http://docs.python-guide.org/en/latest/writing/gotchas/#late-binding-closures">Late Binding Closures</a></li>
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#naming-and-binding">Naming and binding</a></li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @name Loop variable capture
|
||||
* @description Capture of a loop variable is not the same as capturing the value of a loop variable, and may be erroneous.
|
||||
* @kind problem
|
||||
* @tags correctness
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/loop-variable-capture
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
// Gets the scope of the iteration variable of the looping scope
|
||||
Scope iteration_variable_scope(AstNode loop) {
|
||||
result = loop.(For).getScope()
|
||||
or
|
||||
result = loop.(Comp).getFunction()
|
||||
}
|
||||
|
||||
predicate capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) {
|
||||
var.getScope() = iteration_variable_scope(loop) and
|
||||
var.getAnAccess().getScope() = capturing.getInnerScope() and
|
||||
capturing.getParentNode+() = loop and
|
||||
(
|
||||
loop.(For).getTarget() = var.getAnAccess()
|
||||
or
|
||||
var = loop.(Comp).getAnIterationVariable()
|
||||
)
|
||||
}
|
||||
|
||||
predicate escaping_capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) {
|
||||
capturing_looping_construct(capturing, loop, var) and
|
||||
// Escapes if used out side of for loop or is a lambda in a comprehension
|
||||
(
|
||||
loop instanceof For and
|
||||
exists(Expr e | e.pointsTo(_, _, capturing) | not loop.contains(e))
|
||||
or
|
||||
loop.(Comp).getElt() = capturing
|
||||
or
|
||||
loop.(Comp).getElt().(Tuple).getAnElt() = capturing
|
||||
)
|
||||
}
|
||||
|
||||
from CallableExpr capturing, AstNode loop, Variable var
|
||||
where escaping_capturing_looping_construct(capturing, loop, var)
|
||||
select capturing, "Capture of loop variable $@.", loop, var.getId()
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
In Python, a nested function or lambda expression that captures a variable from its surrounding scope is a <em>late-binding</em> closure,
|
||||
meaning that the value of the variable is determined when the closure is called, not when it is created.
|
||||
</p>
|
||||
<p>
|
||||
Care must be taken when the captured variable is a loop variable. If the closure is called after the loop ends, it will use the value of the variable on the last iteration of the loop, rather than the value at the iteration at which it was created.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Ensure that closures that capture loop variables aren't used outside of a single iteration of the loop.
|
||||
To capture the value of a loop variable at the time the closure is created, use a default parameter, or <code>functools.partial</code>.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In the following (BAD) example, a <code>tasks</code> list is created, but each task captures the loop variable <code>i</code>, and reads the same value when run.
|
||||
</p>
|
||||
<sample src="examples/bad.py" />
|
||||
<p>
|
||||
In the following (GOOD) example, each closure has an <code>i</code> default parameter, shadowing the outer <code>i</code> variable, the default value of which is determined as the value of the loop variable <code>i</code> at the time the closure is created.
|
||||
</p>
|
||||
<sample src="examples/good.py" />
|
||||
<p>
|
||||
In the following (GOOD) example, <code>functools.partial</code> is used to partially evaluate the lambda expression with the value of <code>i</code>.
|
||||
</p>
|
||||
<sample src="examples/good2.py" />
|
||||
|
||||
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>The Hitchhiker's Guide to Python: <a href="http://docs.python-guide.org/en/latest/writing/gotchas/#late-binding-closures">Late Binding Closures</a>.</li>
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#naming-and-binding">Naming and binding</a>.</li>
|
||||
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/3431676/creating-functions-or-lambdas-in-a-loop-or-comprehension">Creating functions (or lambdas) in a loop (or comprehension)</a>.</li>
|
||||
<li>Python Language Reference: <a href="https://docs.python.org/3/library/functools.html#functools.partial">functools.partial</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @name Loop variable capture
|
||||
* @description Capturing a loop variable is not the same as capturing its value, and can lead to unexpected behavior or bugs.
|
||||
* @kind path-problem
|
||||
* @tags correctness
|
||||
* quality
|
||||
* @problem.severity error
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/loop-variable-capture
|
||||
*/
|
||||
|
||||
import python
|
||||
import LoopVariableCaptureQuery
|
||||
import EscapingCaptureFlow::PathGraph
|
||||
|
||||
from
|
||||
CallableExpr capturing, AstNode loop, Variable var, string descr,
|
||||
EscapingCaptureFlow::PathNode source, EscapingCaptureFlow::PathNode sink
|
||||
where
|
||||
escapingCapture(capturing, loop, var, source, sink) and
|
||||
if capturing instanceof Lambda then descr = "lambda" else descr = "function"
|
||||
select capturing, source, sink,
|
||||
"This " + descr + " captures the loop variable $@, and may escape the loop by being stored at $@.",
|
||||
loop, var.getId(), sink, "this location"
|
||||
@@ -0,0 +1,81 @@
|
||||
/** Definitions for reasoning about loop variable capture issues. */
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
/** A looping construct. */
|
||||
abstract class Loop extends AstNode {
|
||||
/**
|
||||
* Gets a loop variable of this loop.
|
||||
* For example, `x` and `y` in `for x,y in pairs: print(x+y)`
|
||||
*/
|
||||
abstract Variable getALoopVariable();
|
||||
}
|
||||
|
||||
/** A `for` loop. */
|
||||
private class ForLoop extends Loop, For {
|
||||
override Variable getALoopVariable() {
|
||||
this.getTarget() = result.getAnAccess().getParentNode*() and
|
||||
result.getScope() = this.getScope()
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if the callable `capturing` captures the variable `var` from the loop `loop`. */
|
||||
predicate capturesLoopVariable(CallableExpr capturing, Loop loop, Variable var) {
|
||||
var.getAnAccess().getScope() = capturing.getInnerScope() and
|
||||
capturing.getParentNode+() = loop and
|
||||
var = loop.getALoopVariable()
|
||||
}
|
||||
|
||||
/** Dataflow configuration for reasoning about callables that capture a loop variable and then may escape from the loop. */
|
||||
module EscapingCaptureFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { capturesLoopVariable(node.asExpr(), _, _) }
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
// Stored in a dict/list.
|
||||
exists(Assign assign, Subscript sub |
|
||||
sub = assign.getATarget() and node.asExpr() = assign.getValue()
|
||||
)
|
||||
or
|
||||
// Stored in a list.
|
||||
exists(DataFlow::MethodCallNode mc | mc.calls(_, "append") and node = mc.getArg(0))
|
||||
or
|
||||
// Used in a yield statement, likely included in a collection.
|
||||
// The element of comprehension expressions desugar to involve a yield statement internally.
|
||||
exists(Yield y | node.asExpr() = y.getValue())
|
||||
// Checks for storing in a field leads to false positives, so are omitted.
|
||||
}
|
||||
|
||||
predicate isBarrierOut(DataFlow::Node node) { isSink(node) }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
// Incorrect virtual dispatch to __call__ methods is a source of FPs.
|
||||
exists(Function call |
|
||||
call.getName() = "__call__" and
|
||||
call.getArg(0) = node.(DataFlow::ParameterNode).getParameter()
|
||||
)
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
|
||||
isSink(node) and
|
||||
(
|
||||
cs.(DataFlow::TupleElementContent).getIndex() in [0 .. 10] or
|
||||
cs instanceof DataFlow::ListElementContent or
|
||||
cs instanceof DataFlow::SetElementContent or
|
||||
cs instanceof DataFlow::DictionaryElementAnyContent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Dataflow for reasoning about callables that capture a loop variable and then escape from the loop. */
|
||||
module EscapingCaptureFlow = DataFlow::Global<EscapingCaptureFlowConfig>;
|
||||
|
||||
/** Holds if `capturing` is a callable that captures the variable `var` of the loop `loop`, and then may escape the loop via a flow path from `source` to `sink`. */
|
||||
predicate escapingCapture(
|
||||
CallableExpr capturing, Loop loop, Variable var, EscapingCaptureFlow::PathNode source,
|
||||
EscapingCaptureFlow::PathNode sink
|
||||
) {
|
||||
capturesLoopVariable(capturing, loop, var) and
|
||||
capturing = source.getNode().asExpr() and
|
||||
EscapingCaptureFlow::flowPath(source, sink)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# BAD: The loop variable `i` is captured.
|
||||
tasks = []
|
||||
for i in range(5):
|
||||
tasks.append(lambda: print(i))
|
||||
|
||||
# This will print `4,4,4,4,4`, rather than `0,1,2,3,4` as likely intended.
|
||||
for t in tasks:
|
||||
t()
|
||||
@@ -0,0 +1,8 @@
|
||||
# GOOD: A default parameter is used, so the variable `i` is not being captured.
|
||||
tasks = []
|
||||
for i in range(5):
|
||||
tasks.append(lambda i=i: print(i))
|
||||
|
||||
# This will print `0,1,2,3,4``.
|
||||
for t in tasks:
|
||||
t()
|
||||
@@ -0,0 +1,9 @@
|
||||
import functools
|
||||
# GOOD: `functools.partial` takes care of capturing the _value_ of `i`.
|
||||
tasks = []
|
||||
for i in range(5):
|
||||
tasks.append(functools.partial(lambda i: print(i), i))
|
||||
|
||||
# This will print `0,1,2,3,4``.
|
||||
for t in tasks:
|
||||
t()
|
||||
@@ -34,6 +34,14 @@ predicate complex_all(Module m) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate used_in_forward_declaration(Name used, Module mod) {
|
||||
exists(StringLiteral s, Annotation annotation |
|
||||
s.getS() = used.getId() and
|
||||
s.getEnclosingModule() = mod and
|
||||
annotation.getASubExpression*() = s
|
||||
)
|
||||
}
|
||||
|
||||
predicate unused_global(Name unused, GlobalVariable v) {
|
||||
not exists(ImportingStmt is | is.contains(unused)) and
|
||||
forex(DefinitionNode defn | defn.getNode() = unused |
|
||||
@@ -55,7 +63,8 @@ predicate unused_global(Name unused, GlobalVariable v) {
|
||||
unused.defines(v) and
|
||||
not name_acceptable_for_unused_variable(v) and
|
||||
not complex_all(unused.getEnclosingModule())
|
||||
)
|
||||
) and
|
||||
not used_in_forward_declaration(unused, unused.getEnclosingModule())
|
||||
}
|
||||
|
||||
from Name unused, GlobalVariable v
|
||||
|
||||
3
python/ql/src/change-notes/released/1.4.1.md
Normal file
3
python/ql/src/change-notes/released/1.4.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.4.1
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/src/change-notes/released/1.4.2.md
Normal file
3
python/ql/src/change-notes/released/1.4.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.4.2
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/src/change-notes/released/1.4.3.md
Normal file
3
python/ql/src/change-notes/released/1.4.3.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.4.3
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/src/change-notes/released/1.4.4.md
Normal file
3
python/ql/src/change-notes/released/1.4.4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.4.4
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/src/change-notes/released/1.4.5.md
Normal file
3
python/ql/src/change-notes/released/1.4.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.4.5
|
||||
|
||||
No user-facing changes.
|
||||
9
python/ql/src/change-notes/released/1.4.6.md
Normal file
9
python/ql/src/change-notes/released/1.4.6.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 1.4.6
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- The `py/special-method-wrong-signature` has been modernized and rewritten to no longer rely on outdated APIs. Moreover, the query no longer flags cases where a default value is never used, as these alerts were rarely useful.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- The `py/unused-global-variable` now no longer flags variables that are only used in forward references (e.g. the `Foo` in `def bar(x: "Foo"): ...`).
|
||||
5
python/ql/src/change-notes/released/1.4.7.md
Normal file
5
python/ql/src/change-notes/released/1.4.7.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 1.4.7
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
- The `py/mixed-tuple-returns` query no longer flags instances where the tuple is passed into the function as an argument, as this led to too many false positives.
|
||||
3
python/ql/src/change-notes/released/1.4.8.md
Normal file
3
python/ql/src/change-notes/released/1.4.8.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.4.8
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.4.0
|
||||
lastReleaseVersion: 1.4.8
|
||||
|
||||
7
python/ql/src/codeql-suites/python-code-quality.qls
Normal file
7
python/ql/src/codeql-suites/python-code-quality.qls
Normal file
@@ -0,0 +1,7 @@
|
||||
- queries: .
|
||||
- include:
|
||||
id:
|
||||
- py/not-named-self
|
||||
- py/not-named-cls
|
||||
- py/file-not-closed
|
||||
- py/special-method-wrong-signature
|
||||
@@ -109,6 +109,8 @@ private module TarSlipImprovConfig implements DataFlow::ConfigSig {
|
||||
nodeFrom = nodeTo.(API::CallNode).getArg(0) and
|
||||
nodeFrom = tarfileOpen().getReturn().getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting more "TarSlip" vulnerabilities. */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user